opencastle 0.31.7 → 0.32.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/README.md +4 -0
- package/bin/cli.mjs +15 -0
- package/dist/cli/agents.d.ts.map +1 -1
- package/dist/cli/agents.js +19 -5
- package/dist/cli/agents.js.map +1 -1
- package/dist/cli/artifacts-cli.d.ts +3 -0
- package/dist/cli/artifacts-cli.d.ts.map +1 -0
- package/dist/cli/artifacts-cli.js +36 -0
- package/dist/cli/artifacts-cli.js.map +1 -0
- package/dist/cli/baselines.d.ts.map +1 -1
- package/dist/cli/baselines.js +11 -0
- package/dist/cli/baselines.js.map +1 -1
- package/dist/cli/convoy/artifacts.d.ts +25 -0
- package/dist/cli/convoy/artifacts.d.ts.map +1 -0
- package/dist/cli/convoy/artifacts.js +129 -0
- package/dist/cli/convoy/artifacts.js.map +1 -0
- package/dist/cli/convoy/artifacts.test.d.ts +2 -0
- package/dist/cli/convoy/artifacts.test.d.ts.map +1 -0
- package/dist/cli/convoy/artifacts.test.js +169 -0
- package/dist/cli/convoy/artifacts.test.js.map +1 -0
- package/dist/cli/convoy/compaction.d.ts +23 -0
- package/dist/cli/convoy/compaction.d.ts.map +1 -0
- package/dist/cli/convoy/compaction.js +117 -0
- package/dist/cli/convoy/compaction.js.map +1 -0
- package/dist/cli/convoy/compaction.test.d.ts +2 -0
- package/dist/cli/convoy/compaction.test.d.ts.map +1 -0
- package/dist/cli/convoy/compaction.test.js +205 -0
- package/dist/cli/convoy/compaction.test.js.map +1 -0
- package/dist/cli/convoy/contracts.d.ts +22 -0
- package/dist/cli/convoy/contracts.d.ts.map +1 -0
- package/dist/cli/convoy/contracts.js +254 -0
- package/dist/cli/convoy/contracts.js.map +1 -0
- package/dist/cli/convoy/contracts.test.d.ts +2 -0
- package/dist/cli/convoy/contracts.test.d.ts.map +1 -0
- package/dist/cli/convoy/contracts.test.js +239 -0
- package/dist/cli/convoy/contracts.test.js.map +1 -0
- package/dist/cli/convoy/dag-analysis.d.ts +40 -0
- package/dist/cli/convoy/dag-analysis.d.ts.map +1 -0
- package/dist/cli/convoy/dag-analysis.js +282 -0
- package/dist/cli/convoy/dag-analysis.js.map +1 -0
- package/dist/cli/convoy/dag-analysis.test.d.ts +2 -0
- package/dist/cli/convoy/dag-analysis.test.d.ts.map +1 -0
- package/dist/cli/convoy/dag-analysis.test.js +289 -0
- package/dist/cli/convoy/dag-analysis.test.js.map +1 -0
- package/dist/cli/convoy/effort-scaling.d.ts +20 -0
- package/dist/cli/convoy/effort-scaling.d.ts.map +1 -0
- package/dist/cli/convoy/effort-scaling.js +82 -0
- package/dist/cli/convoy/effort-scaling.js.map +1 -0
- package/dist/cli/convoy/effort-scaling.test.d.ts +2 -0
- package/dist/cli/convoy/effort-scaling.test.d.ts.map +1 -0
- package/dist/cli/convoy/effort-scaling.test.js +120 -0
- package/dist/cli/convoy/effort-scaling.test.js.map +1 -0
- package/dist/cli/convoy/engine.d.ts.map +1 -1
- package/dist/cli/convoy/engine.js +280 -6
- package/dist/cli/convoy/engine.js.map +1 -1
- package/dist/cli/convoy/engine.test.js +155 -18
- package/dist/cli/convoy/engine.test.js.map +1 -1
- package/dist/cli/convoy/event-schemas.d.ts.map +1 -1
- package/dist/cli/convoy/event-schemas.js +55 -0
- package/dist/cli/convoy/event-schemas.js.map +1 -1
- package/dist/cli/convoy/isolation.d.ts +27 -0
- package/dist/cli/convoy/isolation.d.ts.map +1 -0
- package/dist/cli/convoy/isolation.js +120 -0
- package/dist/cli/convoy/isolation.js.map +1 -0
- package/dist/cli/convoy/isolation.test.d.ts +2 -0
- package/dist/cli/convoy/isolation.test.d.ts.map +1 -0
- package/dist/cli/convoy/isolation.test.js +105 -0
- package/dist/cli/convoy/isolation.test.js.map +1 -0
- package/dist/cli/convoy/review-stages.d.ts +9 -0
- package/dist/cli/convoy/review-stages.d.ts.map +1 -0
- package/dist/cli/convoy/review-stages.js +134 -0
- package/dist/cli/convoy/review-stages.js.map +1 -0
- package/dist/cli/convoy/review-stages.test.d.ts +2 -0
- package/dist/cli/convoy/review-stages.test.d.ts.map +1 -0
- package/dist/cli/convoy/review-stages.test.js +197 -0
- package/dist/cli/convoy/review-stages.test.js.map +1 -0
- package/dist/cli/convoy/skill-refinement.d.ts +39 -0
- package/dist/cli/convoy/skill-refinement.d.ts.map +1 -0
- package/dist/cli/convoy/skill-refinement.js +239 -0
- package/dist/cli/convoy/skill-refinement.js.map +1 -0
- package/dist/cli/convoy/skill-refinement.test.d.ts +2 -0
- package/dist/cli/convoy/skill-refinement.test.d.ts.map +1 -0
- package/dist/cli/convoy/skill-refinement.test.js +230 -0
- package/dist/cli/convoy/skill-refinement.test.js.map +1 -0
- package/dist/cli/convoy/spec-builder.d.ts +1 -0
- package/dist/cli/convoy/spec-builder.d.ts.map +1 -1
- package/dist/cli/convoy/spec-builder.js +11 -0
- package/dist/cli/convoy/spec-builder.js.map +1 -1
- package/dist/cli/convoy/spec-builder.test.js +54 -0
- package/dist/cli/convoy/spec-builder.test.js.map +1 -1
- package/dist/cli/convoy/store.d.ts +3 -2
- package/dist/cli/convoy/store.d.ts.map +1 -1
- package/dist/cli/convoy/store.js +20 -2
- package/dist/cli/convoy/store.js.map +1 -1
- package/dist/cli/convoy/store.test.js +15 -15
- package/dist/cli/convoy/store.test.js.map +1 -1
- package/dist/cli/convoy/tdd-gate.d.ts +15 -0
- package/dist/cli/convoy/tdd-gate.d.ts.map +1 -0
- package/dist/cli/convoy/tdd-gate.js +119 -0
- package/dist/cli/convoy/tdd-gate.js.map +1 -0
- package/dist/cli/convoy/tdd-gate.test.d.ts +2 -0
- package/dist/cli/convoy/tdd-gate.test.d.ts.map +1 -0
- package/dist/cli/convoy/tdd-gate.test.js +227 -0
- package/dist/cli/convoy/tdd-gate.test.js.map +1 -0
- package/dist/cli/convoy/types.d.ts +91 -0
- package/dist/cli/convoy/types.d.ts.map +1 -1
- package/dist/cli/convoy/types.js +8 -0
- package/dist/cli/convoy/types.js.map +1 -1
- package/dist/cli/insights.d.ts +3 -0
- package/dist/cli/insights.d.ts.map +1 -0
- package/dist/cli/insights.js +94 -0
- package/dist/cli/insights.js.map +1 -0
- package/dist/cli/lesson.d.ts.map +1 -1
- package/dist/cli/lesson.js +7 -0
- package/dist/cli/lesson.js.map +1 -1
- package/dist/cli/log.d.ts.map +1 -1
- package/dist/cli/log.js +7 -0
- package/dist/cli/log.js.map +1 -1
- package/dist/cli/package-config.d.ts +12 -0
- package/dist/cli/package-config.d.ts.map +1 -0
- package/dist/cli/package-config.js +37 -0
- package/dist/cli/package-config.js.map +1 -0
- package/dist/cli/package.d.ts +23 -0
- package/dist/cli/package.d.ts.map +1 -0
- package/dist/cli/package.js +285 -0
- package/dist/cli/package.js.map +1 -0
- package/dist/cli/package.test.d.ts +2 -0
- package/dist/cli/package.test.d.ts.map +1 -0
- package/dist/cli/package.test.js +236 -0
- package/dist/cli/package.test.js.map +1 -0
- package/dist/cli/pipeline.d.ts +6 -0
- package/dist/cli/pipeline.d.ts.map +1 -1
- package/dist/cli/pipeline.js +15 -2
- package/dist/cli/pipeline.js.map +1 -1
- package/dist/cli/run/schema.d.ts.map +1 -1
- package/dist/cli/run/schema.js +32 -0
- package/dist/cli/run/schema.js.map +1 -1
- package/dist/cli/run/schema.test.js +51 -0
- package/dist/cli/run/schema.test.js.map +1 -1
- package/dist/cli/skills.d.ts +3 -0
- package/dist/cli/skills.d.ts.map +1 -0
- package/dist/cli/skills.js +107 -0
- package/dist/cli/skills.js.map +1 -0
- package/dist/cli/types.d.ts +4 -1
- package/dist/cli/types.d.ts.map +1 -1
- package/package.json +2 -1
- package/src/cli/agents.ts +20 -5
- package/src/cli/artifacts-cli.ts +41 -0
- package/src/cli/baselines.ts +12 -0
- package/src/cli/convoy/artifacts.test.ts +201 -0
- package/src/cli/convoy/artifacts.ts +186 -0
- package/src/cli/convoy/compaction.test.ts +245 -0
- package/src/cli/convoy/compaction.ts +164 -0
- package/src/cli/convoy/contracts.test.ts +279 -0
- package/src/cli/convoy/contracts.ts +280 -0
- package/src/cli/convoy/dag-analysis.test.ts +349 -0
- package/src/cli/convoy/dag-analysis.ts +371 -0
- package/src/cli/convoy/effort-scaling.test.ts +140 -0
- package/src/cli/convoy/effort-scaling.ts +90 -0
- package/src/cli/convoy/engine.test.ts +175 -18
- package/src/cli/convoy/engine.ts +301 -7
- package/src/cli/convoy/event-schemas.ts +55 -0
- package/src/cli/convoy/isolation.test.ts +137 -0
- package/src/cli/convoy/isolation.ts +165 -0
- package/src/cli/convoy/review-stages.test.ts +235 -0
- package/src/cli/convoy/review-stages.ts +166 -0
- package/src/cli/convoy/skill-refinement.test.ts +277 -0
- package/src/cli/convoy/skill-refinement.ts +306 -0
- package/src/cli/convoy/spec-builder.test.ts +61 -0
- package/src/cli/convoy/spec-builder.ts +9 -0
- package/src/cli/convoy/store.test.ts +15 -15
- package/src/cli/convoy/store.ts +26 -4
- package/src/cli/convoy/tdd-gate.test.ts +281 -0
- package/src/cli/convoy/tdd-gate.ts +154 -0
- package/src/cli/convoy/types.ts +51 -0
- package/src/cli/insights.ts +99 -0
- package/src/cli/lesson.ts +8 -0
- package/src/cli/log.ts +8 -0
- package/src/cli/package-config.ts +48 -0
- package/src/cli/package.test.ts +276 -0
- package/src/cli/package.ts +329 -0
- package/src/cli/pipeline.ts +21 -2
- package/src/cli/run/schema.test.ts +58 -0
- package/src/cli/run/schema.ts +33 -0
- package/src/cli/skills.ts +121 -0
- package/src/cli/types.ts +4 -1
- package/src/dashboard/node_modules/.vite/deps/_metadata.json +6 -6
- package/src/orchestrator/prompts/assess-complexity.prompt.md +13 -0
- package/src/orchestrator/prompts/generate-convoy.prompt.md +19 -0
|
@@ -1649,6 +1649,7 @@ function makeTaskRecord(overrides = {}) {
|
|
|
1649
1649
|
dispute_id: null,
|
|
1650
1650
|
drift_score: null,
|
|
1651
1651
|
drift_retried: 0,
|
|
1652
|
+
compaction_count: 0,
|
|
1652
1653
|
...overrides,
|
|
1653
1654
|
};
|
|
1654
1655
|
}
|
|
@@ -1756,7 +1757,8 @@ describe('review pipeline', () => {
|
|
|
1756
1757
|
});
|
|
1757
1758
|
const result = await engine.run();
|
|
1758
1759
|
expect(result.status).toBe('done');
|
|
1759
|
-
|
|
1760
|
+
// Two-stage review: stage 1 (spec compliance) + stage 2 (code quality) = 2 calls
|
|
1761
|
+
expect(mockReviewRunner).toHaveBeenCalledTimes(2);
|
|
1760
1762
|
expect(mockReviewRunner).toHaveBeenCalledWith(expect.objectContaining({ agent: 'developer' }), 'fast', 'default');
|
|
1761
1763
|
});
|
|
1762
1764
|
it('fast review BLOCK + retries remaining — task retried with feedback prepended', async () => {
|
|
@@ -1766,8 +1768,9 @@ describe('review pipeline', () => {
|
|
|
1766
1768
|
return Promise.resolve({ success: true, output: 'ok', exitCode: 0 });
|
|
1767
1769
|
});
|
|
1768
1770
|
const mockReviewRunner = vi.fn()
|
|
1769
|
-
.mockResolvedValueOnce({ verdict: 'block', feedback: 'Missing tests', tokens: 50, model: 'reviewer' })
|
|
1770
|
-
.mockResolvedValueOnce({ verdict: 'pass', feedback: '', tokens: 50, model: 'reviewer' })
|
|
1771
|
+
.mockResolvedValueOnce({ verdict: 'block', feedback: 'Missing tests', tokens: 50, model: 'reviewer' }) // round 1 stage 1 → block (short-circuits)
|
|
1772
|
+
.mockResolvedValueOnce({ verdict: 'pass', feedback: '', tokens: 50, model: 'reviewer' }) // round 2 stage 1 → pass
|
|
1773
|
+
.mockResolvedValueOnce({ verdict: 'pass', feedback: '', tokens: 50, model: 'reviewer' }); // round 2 stage 2 → pass
|
|
1771
1774
|
const engine = makeEngine({
|
|
1772
1775
|
spec: makeSpec({ defaults: { review: 'fast' } }, [{ max_retries: 1 }]),
|
|
1773
1776
|
specYaml: 'name: test',
|
|
@@ -1780,7 +1783,8 @@ describe('review pipeline', () => {
|
|
|
1780
1783
|
const result = await engine.run();
|
|
1781
1784
|
expect(result.status).toBe('done');
|
|
1782
1785
|
expect(adapter.execute).toHaveBeenCalledTimes(2);
|
|
1783
|
-
|
|
1786
|
+
// Round 1: stage 1 blocks (1 call). Round 2: stage 1 pass + stage 2 pass (2 calls). Total: 3
|
|
1787
|
+
expect(mockReviewRunner).toHaveBeenCalledTimes(3);
|
|
1784
1788
|
// Prompt on second attempt should contain feedback
|
|
1785
1789
|
const secondPrompt = adapter.execute.mock.calls[1][0].prompt;
|
|
1786
1790
|
expect(secondPrompt).toContain('Missing tests');
|
|
@@ -1809,10 +1813,10 @@ describe('review pipeline', () => {
|
|
|
1809
1813
|
let callCount = 0;
|
|
1810
1814
|
const mockReviewRunner = vi.fn().mockImplementation(() => {
|
|
1811
1815
|
callCount++;
|
|
1812
|
-
//
|
|
1813
|
-
return Promise.resolve(callCount
|
|
1814
|
-
? { verdict: '
|
|
1815
|
-
: { verdict: '
|
|
1816
|
+
// Reviewer C blocks at stage 1 (call 3); reviewers A and B pass both stages (calls 1,2,4,5)
|
|
1817
|
+
return Promise.resolve(callCount === 3
|
|
1818
|
+
? { verdict: 'block', feedback: 'Minor issue', tokens: 30, model: 'reviewer' }
|
|
1819
|
+
: { verdict: 'pass', feedback: '', tokens: 30, model: 'reviewer' });
|
|
1816
1820
|
});
|
|
1817
1821
|
const engine = makeEngine({
|
|
1818
1822
|
spec: makeSpec({ defaults: { review: 'panel' } }),
|
|
@@ -1825,7 +1829,8 @@ describe('review pipeline', () => {
|
|
|
1825
1829
|
});
|
|
1826
1830
|
const result = await engine.run();
|
|
1827
1831
|
expect(result.status).toBe('done');
|
|
1828
|
-
|
|
1832
|
+
// Two-stage panel: 2 pass reviewers × 2 stages + 1 block reviewer × 1 stage = 5 calls
|
|
1833
|
+
expect(mockReviewRunner).toHaveBeenCalledTimes(5);
|
|
1829
1834
|
});
|
|
1830
1835
|
it('panel review 2/3 BLOCK — task retried with MUST-FIX', async () => {
|
|
1831
1836
|
let reviewCallCount = 0;
|
|
@@ -1875,9 +1880,9 @@ describe('review pipeline', () => {
|
|
|
1875
1880
|
});
|
|
1876
1881
|
const result = await engine.run();
|
|
1877
1882
|
expect(result.status).toBe('done');
|
|
1878
|
-
// first task: budget not exceeded (0 < 100), review runs
|
|
1879
|
-
// second task: budget exceeded (
|
|
1880
|
-
expect(mockReviewRunner).toHaveBeenCalledTimes(
|
|
1883
|
+
// first task: budget not exceeded (0 < 100), two-stage review runs (2 calls, total 400 tokens)
|
|
1884
|
+
// second task: budget exceeded (400 >= 100), review skipped
|
|
1885
|
+
expect(mockReviewRunner).toHaveBeenCalledTimes(2);
|
|
1881
1886
|
});
|
|
1882
1887
|
it('auto route: developer agent with empty diff → auto-pass (no reviewer call)', async () => {
|
|
1883
1888
|
// Given: 'auto' review setting, developer agent, empty diff (git will fail on mock path)
|
|
@@ -1910,7 +1915,7 @@ describe('review pipeline', () => {
|
|
|
1910
1915
|
const store = createConvoyStore(dbPath);
|
|
1911
1916
|
const tasks = store.getTasksByConvoy(result.convoyId);
|
|
1912
1917
|
store.close();
|
|
1913
|
-
expect(tasks[0].review_tokens).toBe(77)
|
|
1918
|
+
expect(tasks[0].review_tokens).toBe(154); // two-stage: 77 (stage 1) + 77 (stage 2)
|
|
1914
1919
|
expect(tasks[0].review_level).toBe('fast');
|
|
1915
1920
|
expect(tasks[0].review_verdict).toBe('pass');
|
|
1916
1921
|
});
|
|
@@ -1952,8 +1957,9 @@ describe('review pipeline', () => {
|
|
|
1952
1957
|
});
|
|
1953
1958
|
it('full fast-review flow: BLOCK on first attempt → retry → PASS → done with complete events', async () => {
|
|
1954
1959
|
const mockReviewRunner = vi.fn()
|
|
1955
|
-
.mockResolvedValueOnce({ verdict: 'block', feedback: 'Add more tests', tokens: 40, model: 'reviewer' })
|
|
1956
|
-
.mockResolvedValueOnce({ verdict: 'pass', feedback: '', tokens: 35, model: 'reviewer' })
|
|
1960
|
+
.mockResolvedValueOnce({ verdict: 'block', feedback: 'Add more tests', tokens: 40, model: 'reviewer' }) // round 1 stage 1 → block
|
|
1961
|
+
.mockResolvedValueOnce({ verdict: 'pass', feedback: '', tokens: 35, model: 'reviewer' }) // round 2 stage 1 → pass
|
|
1962
|
+
.mockResolvedValueOnce({ verdict: 'pass', feedback: '', tokens: 35, model: 'reviewer' }); // round 2 stage 2 → pass
|
|
1957
1963
|
const engine = makeEngine({
|
|
1958
1964
|
spec: makeSpec({ defaults: { review: 'fast' } }, [{ id: 'task-1', max_retries: 1 }]),
|
|
1959
1965
|
specYaml: 'name: test',
|
|
@@ -1966,7 +1972,8 @@ describe('review pipeline', () => {
|
|
|
1966
1972
|
const result = await engine.run();
|
|
1967
1973
|
expect(result.status).toBe('done');
|
|
1968
1974
|
expect(adapter.execute).toHaveBeenCalledTimes(2);
|
|
1969
|
-
|
|
1975
|
+
// Round 1: 1 call (block short-circuits). Round 2: 2 calls (stage 1 + stage 2). Total: 3
|
|
1976
|
+
expect(mockReviewRunner).toHaveBeenCalledTimes(3);
|
|
1970
1977
|
const store = createConvoyStore(dbPath);
|
|
1971
1978
|
const tasks = store.getTasksByConvoy(result.convoyId);
|
|
1972
1979
|
const events = store.getEvents(result.convoyId);
|
|
@@ -2009,7 +2016,9 @@ describe('review pipeline', () => {
|
|
|
2009
2016
|
const result = await engine.run();
|
|
2010
2017
|
expect(result.status).toBe('done');
|
|
2011
2018
|
expect(adapter.execute).toHaveBeenCalledTimes(2);
|
|
2012
|
-
|
|
2019
|
+
// Two-stage panel round 1: 2 blocks ×1 call + 1 pass ×2 calls = 4 calls
|
|
2020
|
+
// Two-stage panel round 2: 3 pass ×2 calls = 6 calls. Total: 10
|
|
2021
|
+
expect(mockReviewRunner).toHaveBeenCalledTimes(10);
|
|
2013
2022
|
const store = createConvoyStore(dbPath);
|
|
2014
2023
|
const tasks = store.getTasksByConvoy(result.convoyId);
|
|
2015
2024
|
store.close();
|
|
@@ -2099,10 +2108,16 @@ describe('drift detection', () => {
|
|
|
2099
2108
|
});
|
|
2100
2109
|
it('detect_drift=true triggers drift check and retries on low confidence', async () => {
|
|
2101
2110
|
// Call sequence: main task → drift check (low score) → main task retry
|
|
2111
|
+
const driftRetryOutput = [
|
|
2112
|
+
'done retry',
|
|
2113
|
+
'<!-- OUTPUT_CONTRACT',
|
|
2114
|
+
'{ "files_changed": ["src/foo.ts"], "tests_added": ["src/foo.test.ts"], "summary": "done" }',
|
|
2115
|
+
'-->',
|
|
2116
|
+
].join('\n');
|
|
2102
2117
|
adapter.execute
|
|
2103
2118
|
.mockResolvedValueOnce({ success: true, output: 'done', exitCode: 0 })
|
|
2104
2119
|
.mockResolvedValueOnce({ success: true, output: '{"score": 0.3, "explanation": "uncertain"}', exitCode: 0 })
|
|
2105
|
-
.mockResolvedValueOnce({ success: true, output:
|
|
2120
|
+
.mockResolvedValueOnce({ success: true, output: driftRetryOutput, exitCode: 0 });
|
|
2106
2121
|
const engine = makeEngine({
|
|
2107
2122
|
spec: makeSpec({ defaults: { detect_drift: true } }, [{ id: 'task-1', max_retries: 1 }]),
|
|
2108
2123
|
specYaml: 'name: test',
|
|
@@ -3183,4 +3198,126 @@ describe('createEventEmitter callsite safety', () => {
|
|
|
3183
3198
|
testStore.close();
|
|
3184
3199
|
});
|
|
3185
3200
|
});
|
|
3201
|
+
// ── Contract retry ────────────────────────────────────────────────────────────
|
|
3202
|
+
describe('contract retry', () => {
|
|
3203
|
+
it('retries when output is missing OUTPUT_CONTRACT and retries remain', async () => {
|
|
3204
|
+
const validContractOutput = [
|
|
3205
|
+
'Work done.',
|
|
3206
|
+
'<!-- OUTPUT_CONTRACT',
|
|
3207
|
+
'{ "files_changed": ["src/foo.ts"], "tests_added": ["src/foo.test.ts"], "summary": "implemented" }',
|
|
3208
|
+
'-->',
|
|
3209
|
+
].join('\n');
|
|
3210
|
+
const adapter = makeAdapter();
|
|
3211
|
+
adapter.execute
|
|
3212
|
+
.mockResolvedValueOnce({ success: true, output: 'no contract here', exitCode: 0 })
|
|
3213
|
+
.mockResolvedValueOnce({ success: true, output: validContractOutput, exitCode: 0 });
|
|
3214
|
+
const engine = makeEngine({
|
|
3215
|
+
spec: makeSpec({}, [{ agent: 'developer', max_retries: 1 }]),
|
|
3216
|
+
specYaml: 'name: test',
|
|
3217
|
+
adapter,
|
|
3218
|
+
dbPath,
|
|
3219
|
+
_worktreeManager: makeWorktreeManager(),
|
|
3220
|
+
_mergeQueue: makeMergeQueue(),
|
|
3221
|
+
});
|
|
3222
|
+
const result = await engine.run();
|
|
3223
|
+
expect(result.status).toBe('done');
|
|
3224
|
+
expect(adapter.execute).toHaveBeenCalledTimes(2);
|
|
3225
|
+
// Second prompt should contain the contract retry message
|
|
3226
|
+
const secondPrompt = adapter.execute.mock.calls[1][0].prompt;
|
|
3227
|
+
expect(secondPrompt).toContain('OUTPUT_CONTRACT');
|
|
3228
|
+
expect(secondPrompt).toContain('Missing fields');
|
|
3229
|
+
const store = createConvoyStore(dbPath);
|
|
3230
|
+
const tasks = store.getTasksByConvoy(result.convoyId);
|
|
3231
|
+
store.close();
|
|
3232
|
+
expect(tasks[0].status).toBe('done');
|
|
3233
|
+
});
|
|
3234
|
+
it('emits contract_violation and marks done when retries exhausted', async () => {
|
|
3235
|
+
const adapter = makeAdapter();
|
|
3236
|
+
adapter.execute.mockResolvedValue({ success: true, output: 'no contract here', exitCode: 0 });
|
|
3237
|
+
const engine = makeEngine({
|
|
3238
|
+
spec: makeSpec({}, [{ agent: 'developer', max_retries: 0 }]),
|
|
3239
|
+
specYaml: 'name: test',
|
|
3240
|
+
adapter,
|
|
3241
|
+
dbPath,
|
|
3242
|
+
_worktreeManager: makeWorktreeManager(),
|
|
3243
|
+
_mergeQueue: makeMergeQueue(),
|
|
3244
|
+
});
|
|
3245
|
+
const result = await engine.run();
|
|
3246
|
+
expect(result.status).toBe('done');
|
|
3247
|
+
expect(adapter.execute).toHaveBeenCalledTimes(1);
|
|
3248
|
+
const store = createConvoyStore(dbPath);
|
|
3249
|
+
const events = store.getEvents(result.convoyId);
|
|
3250
|
+
const tasks = store.getTasksByConvoy(result.convoyId);
|
|
3251
|
+
store.close();
|
|
3252
|
+
const violationEvent = events.find(e => e.type === 'contract_violation');
|
|
3253
|
+
expect(violationEvent).toBeDefined();
|
|
3254
|
+
expect(tasks[0].status).toBe('done');
|
|
3255
|
+
});
|
|
3256
|
+
});
|
|
3257
|
+
// ── Compaction continuation ───────────────────────────────────────────────────
|
|
3258
|
+
describe('compaction continuation', () => {
|
|
3259
|
+
it('re-enqueues without incrementing retries when threshold exceeded', async () => {
|
|
3260
|
+
const adapter = makeAdapter();
|
|
3261
|
+
adapter.execute
|
|
3262
|
+
.mockResolvedValueOnce({ success: true, output: 'phase 1 done', exitCode: 0, usage: { total_tokens: 170_000 } })
|
|
3263
|
+
.mockResolvedValueOnce({ success: true, output: 'all done', exitCode: 0, usage: { total_tokens: 1_000 } });
|
|
3264
|
+
const engine = makeEngine({
|
|
3265
|
+
spec: makeSpec({ defaults: { compaction: { enabled: true, token_threshold_pct: 80, summary_max_tokens: 2000 } } }, [{ model: 'claude-sonnet-4-6', max_retries: 0 }]),
|
|
3266
|
+
specYaml: 'name: test',
|
|
3267
|
+
adapter,
|
|
3268
|
+
dbPath,
|
|
3269
|
+
_worktreeManager: makeWorktreeManager(),
|
|
3270
|
+
_mergeQueue: makeMergeQueue(),
|
|
3271
|
+
});
|
|
3272
|
+
const result = await engine.run();
|
|
3273
|
+
expect(result.status).toBe('done');
|
|
3274
|
+
expect(adapter.execute).toHaveBeenCalledTimes(2);
|
|
3275
|
+
const store = createConvoyStore(dbPath);
|
|
3276
|
+
const events = store.getEvents(result.convoyId);
|
|
3277
|
+
const tasks = store.getTasksByConvoy(result.convoyId);
|
|
3278
|
+
store.close();
|
|
3279
|
+
// Compaction event emitted
|
|
3280
|
+
const compactedEvent = events.find(e => e.type === 'context_compacted');
|
|
3281
|
+
expect(compactedEvent).toBeDefined();
|
|
3282
|
+
// Task completed successfully and retries were NOT incremented by compaction
|
|
3283
|
+
expect(tasks[0].status).toBe('done');
|
|
3284
|
+
expect(tasks[0].retries).toBe(0);
|
|
3285
|
+
});
|
|
3286
|
+
it('fails with context_exhausted when max compactions reached', async () => {
|
|
3287
|
+
const adapter = makeAdapter();
|
|
3288
|
+
// All calls return high token count — will exhaust compaction budget after 3+1 calls
|
|
3289
|
+
adapter.execute.mockResolvedValue({
|
|
3290
|
+
success: true,
|
|
3291
|
+
output: 'partial work',
|
|
3292
|
+
exitCode: 0,
|
|
3293
|
+
usage: { total_tokens: 170_000 },
|
|
3294
|
+
});
|
|
3295
|
+
const engine = makeEngine({
|
|
3296
|
+
spec: makeSpec({ defaults: { compaction: { enabled: true, token_threshold_pct: 80, summary_max_tokens: 2000 } } }, [{ model: 'claude-sonnet-4-6', max_retries: 0 }]),
|
|
3297
|
+
specYaml: 'name: test',
|
|
3298
|
+
adapter,
|
|
3299
|
+
dbPath,
|
|
3300
|
+
_worktreeManager: makeWorktreeManager(),
|
|
3301
|
+
_mergeQueue: makeMergeQueue(),
|
|
3302
|
+
});
|
|
3303
|
+
const result = await engine.run();
|
|
3304
|
+
expect(result.status).toBe('failed');
|
|
3305
|
+
const store = createConvoyStore(dbPath);
|
|
3306
|
+
const events = store.getEvents(result.convoyId);
|
|
3307
|
+
const tasks = store.getTasksByConvoy(result.convoyId);
|
|
3308
|
+
store.close();
|
|
3309
|
+
const exhaustedEvent = events.find(e => {
|
|
3310
|
+
if (e.type !== 'task_failed')
|
|
3311
|
+
return false;
|
|
3312
|
+
try {
|
|
3313
|
+
return JSON.parse(e.data).reason === 'context_exhausted';
|
|
3314
|
+
}
|
|
3315
|
+
catch {
|
|
3316
|
+
return false;
|
|
3317
|
+
}
|
|
3318
|
+
});
|
|
3319
|
+
expect(exhaustedEvent).toBeDefined();
|
|
3320
|
+
expect(tasks[0].status).toBe('failed');
|
|
3321
|
+
});
|
|
3322
|
+
});
|
|
3186
3323
|
//# sourceMappingURL=engine.test.js.map
|