@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.
- package/dist/action-enforcement.d.ts +26 -0
- package/dist/action-enforcement.js +70 -0
- 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 +148 -0
- package/dist/admission-score.js +237 -0
- 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/cycle-utils.d.ts +51 -0
- package/dist/cycle-utils.js +77 -0
- 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/defaults.d.ts +5 -0
- package/dist/defaults.js +5 -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 +295 -30
- package/dist/fix-ci.js +40 -3
- package/dist/fix-review.d.ts +66 -0
- package/dist/fix-review.js +448 -0
- 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 +39 -5
- package/dist/index.js +48 -3
- 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/pipeline-cycle-detector.d.ts +70 -0
- package/dist/pipeline-cycle-detector.js +111 -0
- package/dist/priority.d.ts +7 -76
- 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 +35 -0
- package/dist/review.js +84 -0
- 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 +319 -73
- 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 +5 -1
- package/dist/runners/index.js +4 -0
- package/dist/runners/review-agent.d.ts +67 -0
- package/dist/runners/review-agent.js +350 -0
- 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 +42 -14
- package/dist/runners/types.d.ts +25 -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/index.d.ts +1 -1
- package/dist/state/schema.d.ts +6 -1
- package/dist/state/schema.js +276 -1
- package/dist/state/store.d.ts +71 -1
- package/dist/state/store.js +464 -7
- package/dist/state/types.d.ts +174 -0
- package/dist/types.d.ts +1 -1
- package/dist/validate-agent-output.js +4 -1
- package/dist/watch.js +6 -0
- package/dist/workflow-patterns/artifact-writer.d.ts +16 -0
- package/dist/workflow-patterns/artifact-writer.js +34 -0
- package/dist/workflow-patterns/classifiers.d.ts +10 -0
- package/dist/workflow-patterns/classifiers.js +72 -0
- package/dist/workflow-patterns/detector.d.ts +27 -0
- package/dist/workflow-patterns/detector.js +186 -0
- package/dist/workflow-patterns/index.d.ts +8 -0
- package/dist/workflow-patterns/index.js +7 -0
- package/dist/workflow-patterns/proposal-generator.d.ts +15 -0
- package/dist/workflow-patterns/proposal-generator.js +183 -0
- package/dist/workflow-patterns/telemetry-ingest.d.ts +27 -0
- package/dist/workflow-patterns/telemetry-ingest.js +103 -0
- package/dist/workflow-patterns/types.d.ts +61 -0
- package/dist/workflow-patterns/types.js +11 -0
- package/package.json +2 -2
|
@@ -2,14 +2,35 @@
|
|
|
2
2
|
* Claude Code runner — invokes Claude Code CLI in --print mode
|
|
3
3
|
* and collects the result via git diff.
|
|
4
4
|
*/
|
|
5
|
-
import { spawn
|
|
6
|
-
import { promisify } from 'node:util';
|
|
5
|
+
import { spawn } from 'node:child_process';
|
|
7
6
|
import { DEFAULT_MODEL, DEFAULT_ALLOWED_TOOLS, DEFAULT_RUNNER_TIMEOUT_MS, DEFAULT_LINT_COMMAND, DEFAULT_FORMAT_COMMAND, DEFAULT_COMMIT_MESSAGE_TEMPLATE, DEFAULT_COMMIT_CO_AUTHOR, } from '../defaults.js';
|
|
8
7
|
import { formatContextForPrompt } from '../analysis/context-builder.js';
|
|
9
|
-
|
|
8
|
+
import { gitExec, detectChangedFiles, runAutoFix, snapshotWorktree, detectCrossRepoWrites, } from './git-utils.js';
|
|
9
|
+
/**
|
|
10
|
+
* Build verification step instructions (lint, format, typecheck).
|
|
11
|
+
* Returns an array of numbered step strings starting from the given step number.
|
|
12
|
+
*/
|
|
13
|
+
function buildVerificationSteps(startStep, lintCmd, fmtCmd, typecheckCmd) {
|
|
14
|
+
let step = startStep;
|
|
15
|
+
const lines = [];
|
|
16
|
+
if (lintCmd && fmtCmd) {
|
|
17
|
+
lines.push(`${++step}. After making code changes, run \`${lintCmd}\` and \`${fmtCmd}\` to catch issues before committing.`);
|
|
18
|
+
}
|
|
19
|
+
else if (lintCmd) {
|
|
20
|
+
lines.push(`${++step}. After making code changes, run \`${lintCmd}\` to catch issues before committing.`);
|
|
21
|
+
}
|
|
22
|
+
else if (fmtCmd) {
|
|
23
|
+
lines.push(`${++step}. After making code changes, run \`${fmtCmd}\` to catch issues before committing.`);
|
|
24
|
+
}
|
|
25
|
+
if (typecheckCmd) {
|
|
26
|
+
lines.push(`${++step}. IMPORTANT: Run \`${typecheckCmd}\` to verify there are no TypeScript errors. The pre-commit hook will reject your commit if there are type errors. Fix ALL type errors before committing.`);
|
|
27
|
+
}
|
|
28
|
+
return { lines, nextStep: step };
|
|
29
|
+
}
|
|
10
30
|
export function buildPrompt(ctx) {
|
|
11
31
|
const lintCmd = ctx.lintCommand ?? DEFAULT_LINT_COMMAND;
|
|
12
32
|
const fmtCmd = ctx.formatCommand ?? DEFAULT_FORMAT_COMMAND;
|
|
33
|
+
const typecheckCmd = ctx.typecheckCommand;
|
|
13
34
|
const lines = [
|
|
14
35
|
`You are fixing issue ${/^\d+$/.test(ctx.issueId) ? '#' : ''}${ctx.issueId}: ${ctx.issueTitle}`,
|
|
15
36
|
'',
|
|
@@ -23,29 +44,25 @@ export function buildPrompt(ctx) {
|
|
|
23
44
|
if (fmtCmd) {
|
|
24
45
|
lines.push(`${++step}. If the failure is a formatting/prettier error, run \`${fmtCmd}\` to auto-fix it.`);
|
|
25
46
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
else if (lintCmd) {
|
|
30
|
-
lines.push(`${++step}. After making ANY code changes, always run \`${lintCmd}\` to catch issues before committing.`);
|
|
31
|
-
}
|
|
32
|
-
else if (fmtCmd) {
|
|
33
|
-
lines.push(`${++step}. After making ANY code changes, always run \`${fmtCmd}\` to catch issues before committing.`);
|
|
34
|
-
}
|
|
47
|
+
const ciVerify = buildVerificationSteps(step, lintCmd, fmtCmd, typecheckCmd);
|
|
48
|
+
lines.push(...ciVerify.lines);
|
|
49
|
+
step = ciVerify.nextStep;
|
|
35
50
|
lines.push(`${++step}. Write or update tests if needed to cover your fix.`, `${++step}. NEVER modify files matching the blocked paths below — violations will be automatically detected and the change will be rejected.`, `${++step}. Keep your changes to at most ${ctx.constraints.maxFilesPerChange} files.`);
|
|
36
51
|
}
|
|
52
|
+
else if (ctx.reviewFindings) {
|
|
53
|
+
let step = 0;
|
|
54
|
+
lines.push('## Review Findings', '', ctx.reviewFindings, '', '## Instructions', `${++step}. Read the review findings above carefully.`, `${++step}. Read the relevant source files to understand the context.`, `${++step}. Address all the review findings by making necessary code changes.`, `${++step}. Write or update tests if requested by the reviewers.`);
|
|
55
|
+
const reviewVerify = buildVerificationSteps(step, lintCmd, fmtCmd, typecheckCmd);
|
|
56
|
+
lines.push(...reviewVerify.lines);
|
|
57
|
+
step = reviewVerify.nextStep;
|
|
58
|
+
lines.push(`${++step}. NEVER modify files matching the blocked paths below — violations will be automatically detected and the change will be rejected.`, `${++step}. Keep your changes to at most ${ctx.constraints.maxFilesPerChange} files.`);
|
|
59
|
+
}
|
|
37
60
|
else {
|
|
38
61
|
let step = 0;
|
|
39
62
|
lines.push('## Instructions', `${++step}. Read the relevant source files to understand the codebase.`, `${++step}. Implement the fix or feature described in the issue.`, `${++step}. Write or update tests to cover your changes.`);
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
else if (lintCmd) {
|
|
44
|
-
lines.push(`${++step}. After making code changes, run \`${lintCmd}\` to ensure CI will pass.`);
|
|
45
|
-
}
|
|
46
|
-
else if (fmtCmd) {
|
|
47
|
-
lines.push(`${++step}. After making code changes, run \`${fmtCmd}\` to ensure CI will pass.`);
|
|
48
|
-
}
|
|
63
|
+
const defaultVerify = buildVerificationSteps(step, lintCmd, fmtCmd, typecheckCmd);
|
|
64
|
+
lines.push(...defaultVerify.lines);
|
|
65
|
+
step = defaultVerify.nextStep;
|
|
49
66
|
lines.push(`${++step}. NEVER modify files matching the blocked paths below — violations will be automatically detected and the change will be rejected.`, `${++step}. Keep your changes to at most ${ctx.constraints.maxFilesPerChange} files.`);
|
|
50
67
|
}
|
|
51
68
|
lines.push('', '## Constraints (enforced — violations will be automatically rejected)', `- Maximum files to change: ${ctx.constraints.maxFilesPerChange}`, `- Tests required: ${ctx.constraints.requireTests}`, `- Blocked paths (NEVER modify — changes will be rejected): ${ctx.constraints.blockedPaths.join(', ') || 'none'}`);
|
|
@@ -72,20 +89,161 @@ export function buildPrompt(ctx) {
|
|
|
72
89
|
}
|
|
73
90
|
return lines.join('\n');
|
|
74
91
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
92
|
+
/**
|
|
93
|
+
* Format a stream-json event into a concise, human-readable log line.
|
|
94
|
+
*/
|
|
95
|
+
function formatEventForLog(line) {
|
|
96
|
+
let parsed;
|
|
97
|
+
try {
|
|
98
|
+
parsed = JSON.parse(line);
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
const type = parsed.type;
|
|
104
|
+
if (type === 'system') {
|
|
105
|
+
return `init session=${parsed.session_id?.slice(0, 8)}`;
|
|
106
|
+
}
|
|
107
|
+
if (type === 'assistant') {
|
|
108
|
+
const message = parsed.message;
|
|
109
|
+
const content = message?.content;
|
|
110
|
+
if (!content?.length)
|
|
111
|
+
return null;
|
|
112
|
+
const parts = [];
|
|
113
|
+
for (const block of content) {
|
|
114
|
+
if (block.type === 'thinking') {
|
|
115
|
+
const text = String(block.thinking ?? '');
|
|
116
|
+
parts.push(`thinking: ${text.slice(0, 120)}${text.length > 120 ? '...' : ''}`);
|
|
117
|
+
}
|
|
118
|
+
else if (block.type === 'text') {
|
|
119
|
+
const text = String(block.text ?? '');
|
|
120
|
+
parts.push(`text: ${text.slice(0, 120)}${text.length > 120 ? '...' : ''}`);
|
|
121
|
+
}
|
|
122
|
+
else if (block.type === 'tool_use') {
|
|
123
|
+
const name = String(block.name ?? '');
|
|
124
|
+
const input = (block.input ?? {});
|
|
125
|
+
const file = extractFilePath(input);
|
|
126
|
+
parts.push(file ? `${name}: ${file}` : name);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return parts.join(' | ');
|
|
130
|
+
}
|
|
131
|
+
if (type === 'user') {
|
|
132
|
+
const message = parsed.message;
|
|
133
|
+
const content = message?.content;
|
|
134
|
+
if (!content?.length)
|
|
135
|
+
return null;
|
|
136
|
+
const block = content[0];
|
|
137
|
+
if (block.type === 'tool_result') {
|
|
138
|
+
const text = String(block.content ?? '');
|
|
139
|
+
const truncated = text.slice(0, 80);
|
|
140
|
+
return `result: ${truncated}${text.length > 80 ? '...' : ''}`;
|
|
141
|
+
}
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
if (type === 'result') {
|
|
145
|
+
const cost = parsed.total_cost_usd;
|
|
146
|
+
const turns = parsed.num_turns;
|
|
147
|
+
const duration = parsed.duration_ms;
|
|
148
|
+
return `done — ${turns ?? '?'} turns, ${duration ? Math.round(duration / 1000) + 's' : '?'}, $${cost?.toFixed(4) ?? '?'}`;
|
|
149
|
+
}
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Extract a file path from a tool_use input object.
|
|
154
|
+
*/
|
|
155
|
+
function extractFilePath(input) {
|
|
156
|
+
return ((typeof input.file_path === 'string' ? input.file_path : undefined) ??
|
|
157
|
+
(typeof input.path === 'string' ? input.path : undefined) ??
|
|
158
|
+
(typeof input.pattern === 'string' ? input.pattern : undefined) ??
|
|
159
|
+
(typeof input.command === 'string' ? input.command.slice(0, 80) : undefined));
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Parse a single NDJSON line from Claude Code stream-json output
|
|
163
|
+
* and emit a progress event if applicable.
|
|
164
|
+
*/
|
|
165
|
+
function parseStreamEvent(line, onProgress) {
|
|
166
|
+
let parsed;
|
|
167
|
+
try {
|
|
168
|
+
parsed = JSON.parse(line);
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
return undefined;
|
|
172
|
+
}
|
|
173
|
+
const type = parsed.type;
|
|
174
|
+
if (type === 'assistant') {
|
|
175
|
+
const message = parsed.message;
|
|
176
|
+
const content = message?.content;
|
|
177
|
+
if (!content)
|
|
178
|
+
return undefined;
|
|
179
|
+
for (const block of content) {
|
|
180
|
+
if (block.type === 'tool_use') {
|
|
181
|
+
const toolName = String(block.name ?? '');
|
|
182
|
+
const input = (block.input ?? {});
|
|
183
|
+
const filePath = extractFilePath(input);
|
|
184
|
+
onProgress({
|
|
185
|
+
type: 'tool_start',
|
|
186
|
+
tool: toolName,
|
|
187
|
+
file: filePath,
|
|
188
|
+
message: filePath ? `${toolName}: ${filePath}` : toolName,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
else if (block.type === 'text') {
|
|
192
|
+
const text = String(block.text ?? '');
|
|
193
|
+
if (text.length > 0) {
|
|
194
|
+
onProgress({
|
|
195
|
+
type: 'text',
|
|
196
|
+
message: text.slice(0, 200),
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (type === 'result') {
|
|
203
|
+
const resultText = parsed.result;
|
|
204
|
+
const costUsd = parsed.total_cost_usd;
|
|
205
|
+
// Extract token usage from result event
|
|
206
|
+
const modelUsage = parsed.modelUsage;
|
|
207
|
+
let tokenUsage;
|
|
208
|
+
if (modelUsage) {
|
|
209
|
+
const firstModel = Object.keys(modelUsage)[0];
|
|
210
|
+
if (firstModel) {
|
|
211
|
+
const usage = modelUsage[firstModel];
|
|
212
|
+
tokenUsage = {
|
|
213
|
+
inputTokens: usage.inputTokens ?? 0,
|
|
214
|
+
outputTokens: usage.outputTokens ?? 0,
|
|
215
|
+
cacheReadTokens: usage.cacheReadInputTokens ?? undefined,
|
|
216
|
+
model: firstModel,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
if (costUsd !== undefined) {
|
|
221
|
+
onProgress({ type: 'cost', costUsd, message: `Total cost: $${costUsd.toFixed(4)}` });
|
|
222
|
+
}
|
|
223
|
+
return { resultText: resultText ?? undefined, costUsd, tokenUsage };
|
|
224
|
+
}
|
|
225
|
+
return undefined;
|
|
78
226
|
}
|
|
79
227
|
function runClaude(prompt, workDir, opts) {
|
|
80
228
|
const tools = opts?.allowedTools?.join(',') ?? DEFAULT_ALLOWED_TOOLS;
|
|
81
229
|
const timeoutMs = opts?.timeoutMs ?? DEFAULT_RUNNER_TIMEOUT_MS;
|
|
82
230
|
return new Promise((resolve, reject) => {
|
|
83
231
|
const model = opts?.model ?? process.env.AI_SDLC_MODEL ?? DEFAULT_MODEL;
|
|
84
|
-
const claudeArgs = [
|
|
85
|
-
|
|
232
|
+
const claudeArgs = [
|
|
233
|
+
'-p',
|
|
234
|
+
'--output-format',
|
|
235
|
+
'stream-json',
|
|
236
|
+
'--verbose',
|
|
237
|
+
'--model',
|
|
238
|
+
model,
|
|
239
|
+
'--allowedTools',
|
|
240
|
+
tools,
|
|
241
|
+
];
|
|
242
|
+
// When running inside an OpenShell sandbox, prefix with sandbox connect.
|
|
243
|
+
const useOpenShell = opts?.sandboxId && process.env.AI_SDLC_SANDBOX_PROVIDER === 'openshell';
|
|
86
244
|
let cmd;
|
|
87
245
|
let args;
|
|
88
|
-
if (
|
|
246
|
+
if (useOpenShell) {
|
|
89
247
|
cmd = 'openshell';
|
|
90
248
|
args = ['sandbox', 'connect', opts.sandboxId, '--', 'claude', ...claudeArgs];
|
|
91
249
|
}
|
|
@@ -93,27 +251,114 @@ function runClaude(prompt, workDir, opts) {
|
|
|
93
251
|
cmd = 'claude';
|
|
94
252
|
args = claudeArgs;
|
|
95
253
|
}
|
|
254
|
+
const startTime = Date.now();
|
|
255
|
+
const logPrefix = `[ai-sdlc:runner]`;
|
|
256
|
+
process.stderr.write(`${logPrefix} spawning: ${cmd} ${args.join(' ')}\n`);
|
|
257
|
+
process.stderr.write(`${logPrefix} workDir: ${workDir}\n`);
|
|
258
|
+
process.stderr.write(`${logPrefix} timeout: ${timeoutMs}ms\n`);
|
|
96
259
|
const child = spawn(cmd, args, {
|
|
97
260
|
cwd: workDir,
|
|
98
261
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
99
262
|
env: { ...process.env },
|
|
100
263
|
timeout: timeoutMs,
|
|
101
264
|
});
|
|
102
|
-
|
|
265
|
+
process.stderr.write(`${logPrefix} pid: ${child.pid}\n`);
|
|
266
|
+
let lastActivity = Date.now();
|
|
267
|
+
let resultText;
|
|
268
|
+
let resultCost;
|
|
269
|
+
let resultTokenUsage;
|
|
270
|
+
// Buffer for incomplete NDJSON lines
|
|
271
|
+
let lineBuffer = '';
|
|
272
|
+
const onProgress = opts?.onProgress;
|
|
273
|
+
child.stdout.on('data', (data) => {
|
|
274
|
+
lastActivity = Date.now();
|
|
275
|
+
// Parse NDJSON lines from stream-json output
|
|
276
|
+
lineBuffer += data.toString('utf-8');
|
|
277
|
+
const lines = lineBuffer.split('\n');
|
|
278
|
+
// Keep the last (possibly incomplete) line in the buffer
|
|
279
|
+
lineBuffer = lines.pop() ?? '';
|
|
280
|
+
for (const line of lines) {
|
|
281
|
+
const trimmed = line.trim();
|
|
282
|
+
if (!trimmed)
|
|
283
|
+
continue;
|
|
284
|
+
// Log formatted events to stderr for CI visibility
|
|
285
|
+
const formatted = formatEventForLog(trimmed);
|
|
286
|
+
if (formatted) {
|
|
287
|
+
process.stderr.write(`${logPrefix} ${formatted}\n`);
|
|
288
|
+
}
|
|
289
|
+
if (onProgress) {
|
|
290
|
+
const result = parseStreamEvent(trimmed, onProgress);
|
|
291
|
+
if (result) {
|
|
292
|
+
if (result.resultText !== undefined)
|
|
293
|
+
resultText = result.resultText;
|
|
294
|
+
if (result.costUsd !== undefined)
|
|
295
|
+
resultCost = result.costUsd;
|
|
296
|
+
if (result.tokenUsage)
|
|
297
|
+
resultTokenUsage = result.tokenUsage;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
else {
|
|
301
|
+
// Still parse result event even without progress callback
|
|
302
|
+
try {
|
|
303
|
+
const parsed = JSON.parse(trimmed);
|
|
304
|
+
if (parsed.type === 'result') {
|
|
305
|
+
resultText = parsed.result;
|
|
306
|
+
resultCost = parsed.total_cost_usd;
|
|
307
|
+
const modelUsage = parsed.modelUsage;
|
|
308
|
+
if (modelUsage) {
|
|
309
|
+
const firstModel = Object.keys(modelUsage)[0];
|
|
310
|
+
if (firstModel) {
|
|
311
|
+
const usage = modelUsage[firstModel];
|
|
312
|
+
resultTokenUsage = {
|
|
313
|
+
inputTokens: usage.inputTokens ?? 0,
|
|
314
|
+
outputTokens: usage.outputTokens ?? 0,
|
|
315
|
+
cacheReadTokens: usage.cacheReadInputTokens ?? undefined,
|
|
316
|
+
model: firstModel,
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
catch {
|
|
323
|
+
// Not JSON — ignore
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
});
|
|
103
328
|
const errChunks = [];
|
|
104
|
-
child.
|
|
105
|
-
|
|
329
|
+
child.stderr.on('data', (data) => {
|
|
330
|
+
errChunks.push(data);
|
|
331
|
+
lastActivity = Date.now();
|
|
332
|
+
process.stderr.write(data);
|
|
333
|
+
});
|
|
334
|
+
// Heartbeat: log progress every 30s so CI knows the process is alive
|
|
335
|
+
const heartbeat = setInterval(() => {
|
|
336
|
+
const elapsed = Math.round((Date.now() - startTime) / 1000);
|
|
337
|
+
const idle = Math.round((Date.now() - lastActivity) / 1000);
|
|
338
|
+
process.stderr.write(`${logPrefix} heartbeat: ${elapsed}s elapsed, ${idle}s idle\n`);
|
|
339
|
+
}, 30_000);
|
|
106
340
|
child.on('close', (code) => {
|
|
107
|
-
|
|
341
|
+
clearInterval(heartbeat);
|
|
342
|
+
const elapsed = Math.round((Date.now() - startTime) / 1000);
|
|
108
343
|
const stderr = Buffer.concat(errChunks).toString('utf-8');
|
|
344
|
+
process.stderr.write(`${logPrefix} exited: code=${code} elapsed=${elapsed}s\n`);
|
|
109
345
|
if (code === 0) {
|
|
110
|
-
resolve({
|
|
346
|
+
resolve({
|
|
347
|
+
stdout: resultText ?? '',
|
|
348
|
+
stderr,
|
|
349
|
+
model: resultTokenUsage?.model ?? model,
|
|
350
|
+
costUsd: resultCost,
|
|
351
|
+
});
|
|
111
352
|
}
|
|
112
353
|
else {
|
|
113
|
-
reject(new Error(`claude exited with code ${code}: ${stderr ||
|
|
354
|
+
reject(new Error(`claude exited with code ${code}: ${stderr || resultText || ''}`));
|
|
114
355
|
}
|
|
115
356
|
});
|
|
116
|
-
child.on('error',
|
|
357
|
+
child.on('error', (err) => {
|
|
358
|
+
clearInterval(heartbeat);
|
|
359
|
+
process.stderr.write(`${logPrefix} spawn error: ${err.message}\n`);
|
|
360
|
+
reject(err);
|
|
361
|
+
});
|
|
117
362
|
// Send prompt via stdin and close it
|
|
118
363
|
child.stdin.write(prompt);
|
|
119
364
|
child.stdin.end();
|
|
@@ -152,50 +397,28 @@ export function parseTokenUsage(stderr, model) {
|
|
|
152
397
|
/**
|
|
153
398
|
* Run lint --fix and format commands (best-effort) so pre-commit hooks pass.
|
|
154
399
|
*/
|
|
155
|
-
async function runAutoFix(workDir, lintCmd, fmtCmd) {
|
|
156
|
-
if (fmtCmd) {
|
|
157
|
-
try {
|
|
158
|
-
const [bin, ...args] = fmtCmd.split(' ');
|
|
159
|
-
await execFileAsync(bin, args, { cwd: workDir });
|
|
160
|
-
}
|
|
161
|
-
catch {
|
|
162
|
-
// Format failures are non-fatal — the commit hook will catch remaining issues
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
if (lintCmd) {
|
|
166
|
-
try {
|
|
167
|
-
const [bin, ...args] = lintCmd.split(' ');
|
|
168
|
-
await execFileAsync(bin, args, { cwd: workDir });
|
|
169
|
-
}
|
|
170
|
-
catch {
|
|
171
|
-
// Lint --fix failures are non-fatal
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
400
|
export class ClaudeCodeRunner {
|
|
176
401
|
async run(ctx) {
|
|
177
402
|
const prompt = buildPrompt(ctx);
|
|
178
403
|
try {
|
|
179
|
-
//
|
|
404
|
+
// Snapshot the worktree BEFORE invoking the agent so we can subtract any
|
|
405
|
+
// pre-existing untracked noise (sqlite working files, draft files the user
|
|
406
|
+
// hasn't decided to commit yet, in-flight edits) from the agent's diff.
|
|
407
|
+
// Without this, `git add` would sweep them into the agent's commit.
|
|
408
|
+
const baseline = await snapshotWorktree(ctx.workDir);
|
|
409
|
+
// Invoke Claude Code CLI with stream-json for real-time progress
|
|
180
410
|
const result = await runClaude(prompt, ctx.workDir, {
|
|
181
411
|
allowedTools: ctx.allowedTools,
|
|
182
412
|
timeoutMs: ctx.timeoutMs,
|
|
183
413
|
model: ctx.model,
|
|
184
414
|
sandboxId: ctx.sandboxId,
|
|
415
|
+
onProgress: ctx.onProgress,
|
|
185
416
|
});
|
|
186
|
-
//
|
|
417
|
+
// Token usage from stream-json result event (falls back to stderr parsing)
|
|
187
418
|
const tokenUsage = parseTokenUsage(result.stderr, result.model);
|
|
188
|
-
// Collect changed files
|
|
189
|
-
|
|
190
|
-
const
|
|
191
|
-
'ls-files',
|
|
192
|
-
'--others',
|
|
193
|
-
'--exclude-standard',
|
|
194
|
-
]);
|
|
195
|
-
const filesChanged = [
|
|
196
|
-
...diffOutput.split('\n').filter(Boolean),
|
|
197
|
-
...untrackedOutput.split('\n').filter(Boolean),
|
|
198
|
-
];
|
|
419
|
+
// Collect changed files (uncommitted + agent-committed), excluding
|
|
420
|
+
// pre-existing untracked noise.
|
|
421
|
+
const { filesChanged, agentAlreadyCommitted } = await detectChangedFiles(ctx.workDir, baseline);
|
|
199
422
|
if (filesChanged.length === 0) {
|
|
200
423
|
return {
|
|
201
424
|
success: false,
|
|
@@ -205,14 +428,26 @@ export class ClaudeCodeRunner {
|
|
|
205
428
|
tokenUsage,
|
|
206
429
|
};
|
|
207
430
|
}
|
|
208
|
-
//
|
|
209
|
-
|
|
431
|
+
// If the agent already committed, skip our commit step
|
|
432
|
+
if (agentAlreadyCommitted) {
|
|
433
|
+
return {
|
|
434
|
+
success: true,
|
|
435
|
+
filesChanged,
|
|
436
|
+
summary: result.stdout.slice(0, 2000),
|
|
437
|
+
tokenUsage,
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
// Stage only the files the agent touched (NOT `git add -A`, which
|
|
441
|
+
// would include pre-existing untracked noise per the AISDLC-68 incident).
|
|
442
|
+
await gitExec(ctx.workDir, ['add', '--', ...filesChanged]);
|
|
210
443
|
// Run lint and format before committing to avoid pre-commit hook failures
|
|
211
444
|
const lintCmd = ctx.lintCommand ?? DEFAULT_LINT_COMMAND;
|
|
212
445
|
const fmtCmd = ctx.formatCommand ?? DEFAULT_FORMAT_COMMAND;
|
|
213
446
|
await runAutoFix(ctx.workDir, lintCmd, fmtCmd);
|
|
214
|
-
// Re-stage
|
|
215
|
-
|
|
447
|
+
// Re-stage the same set in case auto-fix re-modified them. Auto-fix should
|
|
448
|
+
// not touch files outside the agent's diff, but if it does, those changes
|
|
449
|
+
// are the user's problem to commit separately.
|
|
450
|
+
await gitExec(ctx.workDir, ['add', '--', ...filesChanged]);
|
|
216
451
|
const tmpl = ctx.commitMessageTemplate ?? DEFAULT_COMMIT_MESSAGE_TEMPLATE;
|
|
217
452
|
const coAuthor = ctx.commitCoAuthor ?? DEFAULT_COMMIT_CO_AUTHOR;
|
|
218
453
|
const commitMsg = tmpl
|
|
@@ -222,11 +457,22 @@ export class ClaudeCodeRunner {
|
|
|
222
457
|
await gitExec(ctx.workDir, ['commit', '-m', `${commitMsg}\n\nCo-Authored-By: ${coAuthor}`]);
|
|
223
458
|
}
|
|
224
459
|
catch {
|
|
225
|
-
// Pre-commit hook may have auto-fixed files — re-stage
|
|
460
|
+
// Pre-commit hook may have auto-fixed files — re-stage the agent's
|
|
461
|
+
// file set and retry once.
|
|
226
462
|
await runAutoFix(ctx.workDir, lintCmd, fmtCmd);
|
|
227
|
-
await gitExec(ctx.workDir, ['add', '
|
|
463
|
+
await gitExec(ctx.workDir, ['add', '--', ...filesChanged]);
|
|
228
464
|
await gitExec(ctx.workDir, ['commit', '-m', `${commitMsg}\n\nCo-Authored-By: ${coAuthor}`]);
|
|
229
465
|
}
|
|
466
|
+
// Detect cross-repo writes (e.g., agent edited files in ../sister-repo)
|
|
467
|
+
// and surface them as a stderr warning so the operator knows to commit
|
|
468
|
+
// them in the sibling repo separately. Not a hard failure — sometimes
|
|
469
|
+
// cross-repo work is intended (the AISDLC-68 docs sync, for example).
|
|
470
|
+
const crossRepoWrites = await detectCrossRepoWrites(ctx.workDir);
|
|
471
|
+
for (const write of crossRepoWrites) {
|
|
472
|
+
process.stderr.write(`[ai-sdlc:runner] WARNING: agent wrote ${write.files.length} file(s) into sibling repo ` +
|
|
473
|
+
`${write.repoPath} — those changes are NOT in this PR. ` +
|
|
474
|
+
`Files: ${write.files.slice(0, 5).join(', ')}${write.files.length > 5 ? ` (+${write.files.length - 5} more)` : ''}\n`);
|
|
475
|
+
}
|
|
230
476
|
return {
|
|
231
477
|
success: true,
|
|
232
478
|
filesChanged,
|
package/dist/runners/codex.js
CHANGED
|
@@ -6,10 +6,13 @@ import { spawn, execFile } from 'node:child_process';
|
|
|
6
6
|
import { promisify } from 'node:util';
|
|
7
7
|
import { DEFAULT_RUNNER_TIMEOUT_MS, DEFAULT_CODEX_MODEL, DEFAULT_COMMIT_MESSAGE_TEMPLATE, DEFAULT_COMMIT_CO_AUTHOR, } from '../defaults.js';
|
|
8
8
|
import { buildPrompt } from './claude-code.js';
|
|
9
|
+
import { cleanGitEnv } from '../runtime/git-env.js';
|
|
9
10
|
export { buildPrompt };
|
|
10
11
|
const execFileAsync = promisify(execFile);
|
|
12
|
+
// Strip GIT_DIR / GIT_WORK_TREE / GIT_INDEX_FILE so git resolves against
|
|
13
|
+
// `workDir`'s own .git rather than whatever a parent process leaked (AISDLC-72).
|
|
11
14
|
async function gitExec(workDir, args) {
|
|
12
|
-
const { stdout } = await execFileAsync('git', args, { cwd: workDir });
|
|
15
|
+
const { stdout } = await execFileAsync('git', args, { cwd: workDir, env: cleanGitEnv() });
|
|
13
16
|
return stdout.trim();
|
|
14
17
|
}
|
|
15
18
|
/**
|
package/dist/runners/copilot.js
CHANGED
|
@@ -6,10 +6,13 @@ import { spawn, execFile } from 'node:child_process';
|
|
|
6
6
|
import { promisify } from 'node:util';
|
|
7
7
|
import { DEFAULT_RUNNER_TIMEOUT_MS, DEFAULT_COPILOT_MODEL, DEFAULT_COMMIT_MESSAGE_TEMPLATE, DEFAULT_COMMIT_CO_AUTHOR, } from '../defaults.js';
|
|
8
8
|
import { buildPrompt } from './claude-code.js';
|
|
9
|
+
import { cleanGitEnv } from '../runtime/git-env.js';
|
|
9
10
|
export { buildPrompt };
|
|
10
11
|
const execFileAsync = promisify(execFile);
|
|
12
|
+
// Strip GIT_DIR / GIT_WORK_TREE / GIT_INDEX_FILE so git resolves against
|
|
13
|
+
// `workDir`'s own .git rather than whatever a parent process leaked (AISDLC-72).
|
|
11
14
|
async function gitExec(workDir, args) {
|
|
12
|
-
const { stdout } = await execFileAsync('git', args, { cwd: workDir });
|
|
15
|
+
const { stdout } = await execFileAsync('git', args, { cwd: workDir, env: cleanGitEnv() });
|
|
13
16
|
return stdout.trim();
|
|
14
17
|
}
|
|
15
18
|
/**
|
package/dist/runners/cursor.js
CHANGED
|
@@ -6,10 +6,13 @@ import { spawn, execFile } from 'node:child_process';
|
|
|
6
6
|
import { promisify } from 'node:util';
|
|
7
7
|
import { DEFAULT_RUNNER_TIMEOUT_MS, DEFAULT_CURSOR_MODEL, DEFAULT_COMMIT_MESSAGE_TEMPLATE, DEFAULT_COMMIT_CO_AUTHOR, } from '../defaults.js';
|
|
8
8
|
import { buildPrompt } from './claude-code.js';
|
|
9
|
+
import { cleanGitEnv } from '../runtime/git-env.js';
|
|
9
10
|
export { buildPrompt };
|
|
10
11
|
const execFileAsync = promisify(execFile);
|
|
12
|
+
// Strip GIT_DIR / GIT_WORK_TREE / GIT_INDEX_FILE so git resolves against
|
|
13
|
+
// `workDir`'s own .git rather than whatever a parent process leaked (AISDLC-72).
|
|
11
14
|
async function gitExec(workDir, args) {
|
|
12
|
-
const { stdout } = await execFileAsync('git', args, { cwd: workDir });
|
|
15
|
+
const { stdout } = await execFileAsync('git', args, { cwd: workDir, env: cleanGitEnv() });
|
|
13
16
|
return stdout.trim();
|
|
14
17
|
}
|
|
15
18
|
/**
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared git utilities for agent runners.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Run a git command in the given directory.
|
|
6
|
+
*
|
|
7
|
+
* Always passes `-c core.quotePath=false` so paths containing non-ASCII
|
|
8
|
+
* characters (e.g. ↔, é, 中) come back as raw UTF-8 instead of git's default
|
|
9
|
+
* octal-escaped form (`"file with \342\206\224.md"`). Without this, the AISDLC-68
|
|
10
|
+
* task file (which has ↔ in its name) showed up in `git diff --name-only`
|
|
11
|
+
* as a quoted+escaped string and the subsequent `git add -- <file>` rejected
|
|
12
|
+
* with "pathspec did not match".
|
|
13
|
+
*
|
|
14
|
+
* Uses `cleanGitEnv()` (AISDLC-72) so git resolves against `workDir`'s own
|
|
15
|
+
* .git rather than whatever a parent process (husky pre-push hook running
|
|
16
|
+
* in another worktree) leaked into GIT_DIR / GIT_WORK_TREE / GIT_INDEX_FILE.
|
|
17
|
+
*/
|
|
18
|
+
export declare function gitExec(workDir: string, args: string[]): Promise<string>;
|
|
19
|
+
export interface DetectedChanges {
|
|
20
|
+
filesChanged: string[];
|
|
21
|
+
agentAlreadyCommitted: boolean;
|
|
22
|
+
}
|
|
23
|
+
export interface WorktreeBaseline {
|
|
24
|
+
/** Set of paths that were untracked BEFORE the agent ran. */
|
|
25
|
+
untracked: Set<string>;
|
|
26
|
+
/** Set of paths that had unstaged modifications BEFORE the agent ran. */
|
|
27
|
+
modified: Set<string>;
|
|
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 declare function snapshotWorktree(workDir: string): Promise<WorktreeBaseline>;
|
|
40
|
+
/**
|
|
41
|
+
* Detect files changed by an agent. Three signals contribute:
|
|
42
|
+
*
|
|
43
|
+
* - **Unstaged working-tree changes** (`git diff --name-only`)
|
|
44
|
+
* - **Staged but uncommitted changes** (`git diff --name-only --cached`) — the
|
|
45
|
+
* agent sometimes runs `git add` itself before yielding control. Without
|
|
46
|
+
* this signal the orchestrator returned "Agent made no changes" and bailed
|
|
47
|
+
* even though the agent's work was sitting in the index (the AISDLC-68
|
|
48
|
+
* fourth-rerun bug).
|
|
49
|
+
* - **Untracked files** (`git ls-files --others`)
|
|
50
|
+
*
|
|
51
|
+
* When `baseline` is provided, untracked files that existed BEFORE the agent
|
|
52
|
+
* ran are excluded — they belong to the user, not the agent's diff.
|
|
53
|
+
*
|
|
54
|
+
* Also checks whether the agent self-committed (compares HEAD to merge-base
|
|
55
|
+
* with origin/main). Self-committed runs short-circuit the orchestrator's
|
|
56
|
+
* commit step.
|
|
57
|
+
*/
|
|
58
|
+
export declare function detectChangedFiles(workDir: string, baseline?: WorktreeBaseline): Promise<DetectedChanges>;
|
|
59
|
+
export interface CrossRepoWrite {
|
|
60
|
+
/** Sibling repository absolute path. */
|
|
61
|
+
repoPath: string;
|
|
62
|
+
/** Files modified or added in that sibling, relative to its root. */
|
|
63
|
+
files: string[];
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Detect writes the agent made into sibling git repositories (i.e., directories
|
|
67
|
+
* adjacent to `workDir` that are themselves git repos). Surfaced as a warning,
|
|
68
|
+
* not a hard failure — the AISDLC-68 task LEGITIMATELY needed to sync into
|
|
69
|
+
* `../ai-sdlc-io/`, and we don't want to forbid that. The orchestrator just
|
|
70
|
+
* needs to surface that the changes exist so the operator knows to commit them
|
|
71
|
+
* separately in the sibling repo.
|
|
72
|
+
*
|
|
73
|
+
* Returns one entry per dirty sibling repo, with the list of changed files.
|
|
74
|
+
* Empty array when no siblings are dirty (the common case).
|
|
75
|
+
*/
|
|
76
|
+
export declare function detectCrossRepoWrites(workDir: string): Promise<CrossRepoWrite[]>;
|
|
77
|
+
/**
|
|
78
|
+
* Run lint and format auto-fix commands (best-effort, non-fatal).
|
|
79
|
+
*/
|
|
80
|
+
export declare function runAutoFix(workDir: string, lintCmd?: string, fmtCmd?: string): Promise<void>;
|
|
81
|
+
//# sourceMappingURL=git-utils.d.ts.map
|