@renseiai/agentfactory 0.8.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/LICENSE +21 -0
- package/README.md +125 -0
- package/dist/src/config/index.d.ts +3 -0
- package/dist/src/config/index.d.ts.map +1 -0
- package/dist/src/config/index.js +1 -0
- package/dist/src/config/repository-config.d.ts +44 -0
- package/dist/src/config/repository-config.d.ts.map +1 -0
- package/dist/src/config/repository-config.js +88 -0
- package/dist/src/config/repository-config.test.d.ts +2 -0
- package/dist/src/config/repository-config.test.d.ts.map +1 -0
- package/dist/src/config/repository-config.test.js +249 -0
- package/dist/src/deployment/deployment-checker.d.ts +110 -0
- package/dist/src/deployment/deployment-checker.d.ts.map +1 -0
- package/dist/src/deployment/deployment-checker.js +242 -0
- package/dist/src/deployment/index.d.ts +3 -0
- package/dist/src/deployment/index.d.ts.map +1 -0
- package/dist/src/deployment/index.js +2 -0
- package/dist/src/frontend/index.d.ts +2 -0
- package/dist/src/frontend/index.d.ts.map +1 -0
- package/dist/src/frontend/index.js +1 -0
- package/dist/src/frontend/types.d.ts +106 -0
- package/dist/src/frontend/types.d.ts.map +1 -0
- package/dist/src/frontend/types.js +11 -0
- package/dist/src/governor/decision-engine.d.ts +52 -0
- package/dist/src/governor/decision-engine.d.ts.map +1 -0
- package/dist/src/governor/decision-engine.js +220 -0
- package/dist/src/governor/decision-engine.test.d.ts +2 -0
- package/dist/src/governor/decision-engine.test.d.ts.map +1 -0
- package/dist/src/governor/decision-engine.test.js +629 -0
- package/dist/src/governor/event-bus.d.ts +43 -0
- package/dist/src/governor/event-bus.d.ts.map +1 -0
- package/dist/src/governor/event-bus.js +8 -0
- package/dist/src/governor/event-deduplicator.d.ts +43 -0
- package/dist/src/governor/event-deduplicator.d.ts.map +1 -0
- package/dist/src/governor/event-deduplicator.js +53 -0
- package/dist/src/governor/event-driven-governor.d.ts +131 -0
- package/dist/src/governor/event-driven-governor.d.ts.map +1 -0
- package/dist/src/governor/event-driven-governor.js +379 -0
- package/dist/src/governor/event-driven-governor.test.d.ts +2 -0
- package/dist/src/governor/event-driven-governor.test.d.ts.map +1 -0
- package/dist/src/governor/event-driven-governor.test.js +673 -0
- package/dist/src/governor/event-types.d.ts +78 -0
- package/dist/src/governor/event-types.d.ts.map +1 -0
- package/dist/src/governor/event-types.js +32 -0
- package/dist/src/governor/governor-types.d.ts +82 -0
- package/dist/src/governor/governor-types.d.ts.map +1 -0
- package/dist/src/governor/governor-types.js +21 -0
- package/dist/src/governor/governor.d.ts +100 -0
- package/dist/src/governor/governor.d.ts.map +1 -0
- package/dist/src/governor/governor.js +262 -0
- package/dist/src/governor/governor.test.d.ts +2 -0
- package/dist/src/governor/governor.test.d.ts.map +1 -0
- package/dist/src/governor/governor.test.js +514 -0
- package/dist/src/governor/human-touchpoints.d.ts +131 -0
- package/dist/src/governor/human-touchpoints.d.ts.map +1 -0
- package/dist/src/governor/human-touchpoints.js +251 -0
- package/dist/src/governor/human-touchpoints.test.d.ts +2 -0
- package/dist/src/governor/human-touchpoints.test.d.ts.map +1 -0
- package/dist/src/governor/human-touchpoints.test.js +366 -0
- package/dist/src/governor/in-memory-event-bus.d.ts +29 -0
- package/dist/src/governor/in-memory-event-bus.d.ts.map +1 -0
- package/dist/src/governor/in-memory-event-bus.js +79 -0
- package/dist/src/governor/index.d.ts +14 -0
- package/dist/src/governor/index.d.ts.map +1 -0
- package/dist/src/governor/index.js +13 -0
- package/dist/src/governor/override-parser.d.ts +60 -0
- package/dist/src/governor/override-parser.d.ts.map +1 -0
- package/dist/src/governor/override-parser.js +98 -0
- package/dist/src/governor/override-parser.test.d.ts +2 -0
- package/dist/src/governor/override-parser.test.d.ts.map +1 -0
- package/dist/src/governor/override-parser.test.js +312 -0
- package/dist/src/governor/platform-adapter.d.ts +69 -0
- package/dist/src/governor/platform-adapter.d.ts.map +1 -0
- package/dist/src/governor/platform-adapter.js +11 -0
- package/dist/src/governor/processing-state.d.ts +66 -0
- package/dist/src/governor/processing-state.d.ts.map +1 -0
- package/dist/src/governor/processing-state.js +43 -0
- package/dist/src/governor/processing-state.test.d.ts +2 -0
- package/dist/src/governor/processing-state.test.d.ts.map +1 -0
- package/dist/src/governor/processing-state.test.js +96 -0
- package/dist/src/governor/top-of-funnel.d.ts +118 -0
- package/dist/src/governor/top-of-funnel.d.ts.map +1 -0
- package/dist/src/governor/top-of-funnel.js +168 -0
- package/dist/src/governor/top-of-funnel.test.d.ts +2 -0
- package/dist/src/governor/top-of-funnel.test.d.ts.map +1 -0
- package/dist/src/governor/top-of-funnel.test.js +331 -0
- package/dist/src/index.d.ts +11 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +10 -0
- package/dist/src/linear-cli.d.ts +38 -0
- package/dist/src/linear-cli.d.ts.map +1 -0
- package/dist/src/linear-cli.js +674 -0
- package/dist/src/logger.d.ts +117 -0
- package/dist/src/logger.d.ts.map +1 -0
- package/dist/src/logger.js +430 -0
- package/dist/src/manifest/generate.d.ts +20 -0
- package/dist/src/manifest/generate.d.ts.map +1 -0
- package/dist/src/manifest/generate.js +65 -0
- package/dist/src/manifest/index.d.ts +4 -0
- package/dist/src/manifest/index.d.ts.map +1 -0
- package/dist/src/manifest/index.js +2 -0
- package/dist/src/manifest/route-manifest.d.ts +34 -0
- package/dist/src/manifest/route-manifest.d.ts.map +1 -0
- package/dist/src/manifest/route-manifest.js +148 -0
- package/dist/src/orchestrator/activity-emitter.d.ts +119 -0
- package/dist/src/orchestrator/activity-emitter.d.ts.map +1 -0
- package/dist/src/orchestrator/activity-emitter.js +306 -0
- package/dist/src/orchestrator/api-activity-emitter.d.ts +167 -0
- package/dist/src/orchestrator/api-activity-emitter.d.ts.map +1 -0
- package/dist/src/orchestrator/api-activity-emitter.js +417 -0
- package/dist/src/orchestrator/heartbeat-writer.d.ts +57 -0
- package/dist/src/orchestrator/heartbeat-writer.d.ts.map +1 -0
- package/dist/src/orchestrator/heartbeat-writer.js +137 -0
- package/dist/src/orchestrator/index.d.ts +20 -0
- package/dist/src/orchestrator/index.d.ts.map +1 -0
- package/dist/src/orchestrator/index.js +22 -0
- package/dist/src/orchestrator/log-analyzer.d.ts +160 -0
- package/dist/src/orchestrator/log-analyzer.d.ts.map +1 -0
- package/dist/src/orchestrator/log-analyzer.js +572 -0
- package/dist/src/orchestrator/log-config.d.ts +39 -0
- package/dist/src/orchestrator/log-config.d.ts.map +1 -0
- package/dist/src/orchestrator/log-config.js +45 -0
- package/dist/src/orchestrator/orchestrator.d.ts +316 -0
- package/dist/src/orchestrator/orchestrator.d.ts.map +1 -0
- package/dist/src/orchestrator/orchestrator.js +3290 -0
- package/dist/src/orchestrator/parse-work-result.d.ts +16 -0
- package/dist/src/orchestrator/parse-work-result.d.ts.map +1 -0
- package/dist/src/orchestrator/parse-work-result.js +135 -0
- package/dist/src/orchestrator/parse-work-result.test.d.ts +2 -0
- package/dist/src/orchestrator/parse-work-result.test.d.ts.map +1 -0
- package/dist/src/orchestrator/parse-work-result.test.js +234 -0
- package/dist/src/orchestrator/progress-logger.d.ts +72 -0
- package/dist/src/orchestrator/progress-logger.d.ts.map +1 -0
- package/dist/src/orchestrator/progress-logger.js +135 -0
- package/dist/src/orchestrator/session-logger.d.ts +159 -0
- package/dist/src/orchestrator/session-logger.d.ts.map +1 -0
- package/dist/src/orchestrator/session-logger.js +275 -0
- package/dist/src/orchestrator/state-recovery.d.ts +96 -0
- package/dist/src/orchestrator/state-recovery.d.ts.map +1 -0
- package/dist/src/orchestrator/state-recovery.js +302 -0
- package/dist/src/orchestrator/state-types.d.ts +165 -0
- package/dist/src/orchestrator/state-types.d.ts.map +1 -0
- package/dist/src/orchestrator/state-types.js +7 -0
- package/dist/src/orchestrator/stream-parser.d.ts +151 -0
- package/dist/src/orchestrator/stream-parser.d.ts.map +1 -0
- package/dist/src/orchestrator/stream-parser.js +137 -0
- package/dist/src/orchestrator/types.d.ts +232 -0
- package/dist/src/orchestrator/types.d.ts.map +1 -0
- package/dist/src/orchestrator/types.js +4 -0
- package/dist/src/orchestrator/validate-git-remote.test.d.ts +2 -0
- package/dist/src/orchestrator/validate-git-remote.test.d.ts.map +1 -0
- package/dist/src/orchestrator/validate-git-remote.test.js +61 -0
- package/dist/src/providers/a2a-auth.d.ts +81 -0
- package/dist/src/providers/a2a-auth.d.ts.map +1 -0
- package/dist/src/providers/a2a-auth.js +188 -0
- package/dist/src/providers/a2a-auth.test.d.ts +2 -0
- package/dist/src/providers/a2a-auth.test.d.ts.map +1 -0
- package/dist/src/providers/a2a-auth.test.js +232 -0
- package/dist/src/providers/a2a-provider.d.ts +254 -0
- package/dist/src/providers/a2a-provider.d.ts.map +1 -0
- package/dist/src/providers/a2a-provider.integration.test.d.ts +9 -0
- package/dist/src/providers/a2a-provider.integration.test.d.ts.map +1 -0
- package/dist/src/providers/a2a-provider.integration.test.js +665 -0
- package/dist/src/providers/a2a-provider.js +811 -0
- package/dist/src/providers/a2a-provider.test.d.ts +2 -0
- package/dist/src/providers/a2a-provider.test.d.ts.map +1 -0
- package/dist/src/providers/a2a-provider.test.js +681 -0
- package/dist/src/providers/amp-provider.d.ts +20 -0
- package/dist/src/providers/amp-provider.d.ts.map +1 -0
- package/dist/src/providers/amp-provider.js +24 -0
- package/dist/src/providers/claude-provider.d.ts +18 -0
- package/dist/src/providers/claude-provider.d.ts.map +1 -0
- package/dist/src/providers/claude-provider.js +437 -0
- package/dist/src/providers/codex-provider.d.ts +133 -0
- package/dist/src/providers/codex-provider.d.ts.map +1 -0
- package/dist/src/providers/codex-provider.js +381 -0
- package/dist/src/providers/codex-provider.test.d.ts +2 -0
- package/dist/src/providers/codex-provider.test.d.ts.map +1 -0
- package/dist/src/providers/codex-provider.test.js +387 -0
- package/dist/src/providers/index.d.ts +44 -0
- package/dist/src/providers/index.d.ts.map +1 -0
- package/dist/src/providers/index.js +85 -0
- package/dist/src/providers/spring-ai-provider.d.ts +90 -0
- package/dist/src/providers/spring-ai-provider.d.ts.map +1 -0
- package/dist/src/providers/spring-ai-provider.integration.test.d.ts +13 -0
- package/dist/src/providers/spring-ai-provider.integration.test.d.ts.map +1 -0
- package/dist/src/providers/spring-ai-provider.integration.test.js +351 -0
- package/dist/src/providers/spring-ai-provider.js +317 -0
- package/dist/src/providers/spring-ai-provider.test.d.ts +2 -0
- package/dist/src/providers/spring-ai-provider.test.d.ts.map +1 -0
- package/dist/src/providers/spring-ai-provider.test.js +200 -0
- package/dist/src/providers/types.d.ts +165 -0
- package/dist/src/providers/types.d.ts.map +1 -0
- package/dist/src/providers/types.js +13 -0
- package/dist/src/templates/adapters.d.ts +51 -0
- package/dist/src/templates/adapters.d.ts.map +1 -0
- package/dist/src/templates/adapters.js +104 -0
- package/dist/src/templates/adapters.test.d.ts +2 -0
- package/dist/src/templates/adapters.test.d.ts.map +1 -0
- package/dist/src/templates/adapters.test.js +165 -0
- package/dist/src/templates/agent-definition.d.ts +85 -0
- package/dist/src/templates/agent-definition.d.ts.map +1 -0
- package/dist/src/templates/agent-definition.js +97 -0
- package/dist/src/templates/agent-definition.test.d.ts +2 -0
- package/dist/src/templates/agent-definition.test.d.ts.map +1 -0
- package/dist/src/templates/agent-definition.test.js +209 -0
- package/dist/src/templates/index.d.ts +14 -0
- package/dist/src/templates/index.d.ts.map +1 -0
- package/dist/src/templates/index.js +11 -0
- package/dist/src/templates/loader.d.ts +41 -0
- package/dist/src/templates/loader.d.ts.map +1 -0
- package/dist/src/templates/loader.js +114 -0
- package/dist/src/templates/registry.d.ts +80 -0
- package/dist/src/templates/registry.d.ts.map +1 -0
- package/dist/src/templates/registry.js +177 -0
- package/dist/src/templates/registry.test.d.ts +2 -0
- package/dist/src/templates/registry.test.d.ts.map +1 -0
- package/dist/src/templates/registry.test.js +198 -0
- package/dist/src/templates/renderer.d.ts +29 -0
- package/dist/src/templates/renderer.d.ts.map +1 -0
- package/dist/src/templates/renderer.js +35 -0
- package/dist/src/templates/strategy-templates.test.d.ts +2 -0
- package/dist/src/templates/strategy-templates.test.d.ts.map +1 -0
- package/dist/src/templates/strategy-templates.test.js +619 -0
- package/dist/src/templates/types.d.ts +233 -0
- package/dist/src/templates/types.d.ts.map +1 -0
- package/dist/src/templates/types.js +127 -0
- package/dist/src/templates/types.test.d.ts +2 -0
- package/dist/src/templates/types.test.d.ts.map +1 -0
- package/dist/src/templates/types.test.js +232 -0
- package/dist/src/tools/index.d.ts +6 -0
- package/dist/src/tools/index.d.ts.map +1 -0
- package/dist/src/tools/index.js +3 -0
- package/dist/src/tools/linear-runner.d.ts +34 -0
- package/dist/src/tools/linear-runner.d.ts.map +1 -0
- package/dist/src/tools/linear-runner.js +700 -0
- package/dist/src/tools/plugins/linear.d.ts +9 -0
- package/dist/src/tools/plugins/linear.d.ts.map +1 -0
- package/dist/src/tools/plugins/linear.js +138 -0
- package/dist/src/tools/registry.d.ts +9 -0
- package/dist/src/tools/registry.d.ts.map +1 -0
- package/dist/src/tools/registry.js +18 -0
- package/dist/src/tools/types.d.ts +18 -0
- package/dist/src/tools/types.d.ts.map +1 -0
- package/dist/src/tools/types.js +1 -0
- package/package.json +78 -0
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
+
import { needsResearch, isReadyForBacklogCreation, isWellResearched, determineTopOfFunnelAction, DEFAULT_TOP_OF_FUNNEL_CONFIG, } from './top-of-funnel.js';
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// Helpers
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
/** Create a minimal IssueInfo with sensible defaults. */
|
|
7
|
+
function makeIssue(overrides = {}) {
|
|
8
|
+
return {
|
|
9
|
+
id: 'issue-1',
|
|
10
|
+
identifier: 'SUP-100',
|
|
11
|
+
title: 'Test Issue',
|
|
12
|
+
description: undefined,
|
|
13
|
+
status: 'Icebox',
|
|
14
|
+
labels: [],
|
|
15
|
+
createdAt: Date.now() - 2 * 60 * 60 * 1000, // 2 hours ago (past delay)
|
|
16
|
+
...overrides,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/** Build a well-researched description (long + structured header). */
|
|
20
|
+
function wellResearchedDescription() {
|
|
21
|
+
return ('## Acceptance Criteria\n' +
|
|
22
|
+
'- The system should handle X\n' +
|
|
23
|
+
'- The system should handle Y\n' +
|
|
24
|
+
'\n' +
|
|
25
|
+
'## Technical Approach\n' +
|
|
26
|
+
'We will implement this by modifying the governor module to add top-of-funnel ' +
|
|
27
|
+
'triggers that automatically research issues in the Icebox. This approach ' +
|
|
28
|
+
'ensures issues are enriched before entering the active backlog.');
|
|
29
|
+
}
|
|
30
|
+
/** Build a sparse description (too short / no headers). */
|
|
31
|
+
function sparseDescription() {
|
|
32
|
+
return 'Fix the thing.';
|
|
33
|
+
}
|
|
34
|
+
/** Default context where nothing blocks the action. */
|
|
35
|
+
function defaultContext(overrides = {}) {
|
|
36
|
+
return {
|
|
37
|
+
hasActiveSession: false,
|
|
38
|
+
isHeld: false,
|
|
39
|
+
researchCompleted: false,
|
|
40
|
+
backlogCreationCompleted: false,
|
|
41
|
+
isParentIssue: false,
|
|
42
|
+
...overrides,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// isWellResearched
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
describe('isWellResearched', () => {
|
|
49
|
+
it('returns false for undefined description', () => {
|
|
50
|
+
expect(isWellResearched(undefined)).toBe(false);
|
|
51
|
+
});
|
|
52
|
+
it('returns false for empty description', () => {
|
|
53
|
+
expect(isWellResearched('')).toBe(false);
|
|
54
|
+
});
|
|
55
|
+
it('returns false for short description even with a header', () => {
|
|
56
|
+
expect(isWellResearched('## Acceptance Criteria\nDone.')).toBe(false);
|
|
57
|
+
});
|
|
58
|
+
it('returns false for long description without structured headers', () => {
|
|
59
|
+
const longNoHeaders = 'A'.repeat(300);
|
|
60
|
+
expect(isWellResearched(longNoHeaders)).toBe(false);
|
|
61
|
+
});
|
|
62
|
+
it('returns true for description meeting both length and header criteria', () => {
|
|
63
|
+
expect(isWellResearched(wellResearchedDescription())).toBe(true);
|
|
64
|
+
});
|
|
65
|
+
it('recognises all configured headers', () => {
|
|
66
|
+
for (const header of DEFAULT_TOP_OF_FUNNEL_CONFIG.researchedHeaders) {
|
|
67
|
+
const desc = `${header}\n${'x'.repeat(250)}`;
|
|
68
|
+
expect(isWellResearched(desc)).toBe(true);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
it('respects custom config with different length threshold', () => {
|
|
72
|
+
const config = {
|
|
73
|
+
...DEFAULT_TOP_OF_FUNNEL_CONFIG,
|
|
74
|
+
minResearchedDescriptionLength: 10,
|
|
75
|
+
};
|
|
76
|
+
// Short but has a header and meets reduced threshold
|
|
77
|
+
expect(isWellResearched('## Summary\nShort text', config)).toBe(true);
|
|
78
|
+
});
|
|
79
|
+
it('respects custom config with different headers', () => {
|
|
80
|
+
const config = {
|
|
81
|
+
...DEFAULT_TOP_OF_FUNNEL_CONFIG,
|
|
82
|
+
researchedHeaders: ['## Custom Header'],
|
|
83
|
+
};
|
|
84
|
+
const desc = `## Custom Header\n${'x'.repeat(250)}`;
|
|
85
|
+
expect(isWellResearched(desc, config)).toBe(true);
|
|
86
|
+
// Default headers should no longer match
|
|
87
|
+
expect(isWellResearched(wellResearchedDescription(), config)).toBe(false);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
// ---------------------------------------------------------------------------
|
|
91
|
+
// needsResearch
|
|
92
|
+
// ---------------------------------------------------------------------------
|
|
93
|
+
describe('needsResearch', () => {
|
|
94
|
+
beforeEach(() => {
|
|
95
|
+
vi.useFakeTimers();
|
|
96
|
+
vi.setSystemTime(new Date('2025-06-01T12:00:00Z'));
|
|
97
|
+
});
|
|
98
|
+
afterEach(() => {
|
|
99
|
+
vi.useRealTimers();
|
|
100
|
+
});
|
|
101
|
+
it('returns false if issue is not in Icebox', () => {
|
|
102
|
+
const issue = makeIssue({ status: 'Backlog' });
|
|
103
|
+
expect(needsResearch(issue)).toBe(false);
|
|
104
|
+
});
|
|
105
|
+
it('returns false if issue has not been in Icebox long enough', () => {
|
|
106
|
+
// Created 30 minutes ago — default delay is 1 hour
|
|
107
|
+
const issue = makeIssue({ createdAt: Date.now() - 30 * 60 * 1000 });
|
|
108
|
+
expect(needsResearch(issue)).toBe(false);
|
|
109
|
+
});
|
|
110
|
+
it('returns true for old Icebox issue with sparse description', () => {
|
|
111
|
+
const issue = makeIssue({ description: sparseDescription() });
|
|
112
|
+
expect(needsResearch(issue)).toBe(true);
|
|
113
|
+
});
|
|
114
|
+
it('returns true for old Icebox issue with no description', () => {
|
|
115
|
+
const issue = makeIssue({ description: undefined });
|
|
116
|
+
expect(needsResearch(issue)).toBe(true);
|
|
117
|
+
});
|
|
118
|
+
it('returns false for old Icebox issue with well-researched description', () => {
|
|
119
|
+
const issue = makeIssue({ description: wellResearchedDescription() });
|
|
120
|
+
expect(needsResearch(issue)).toBe(false);
|
|
121
|
+
});
|
|
122
|
+
it('returns true when issue has "Needs Research" label even with well-researched description', () => {
|
|
123
|
+
const issue = makeIssue({
|
|
124
|
+
description: wellResearchedDescription(),
|
|
125
|
+
labels: ['Needs Research'],
|
|
126
|
+
});
|
|
127
|
+
expect(needsResearch(issue)).toBe(true);
|
|
128
|
+
});
|
|
129
|
+
it('returns false for parent issues (they use coordination)', () => {
|
|
130
|
+
const issue = makeIssue({ parentId: 'parent-1', description: sparseDescription() });
|
|
131
|
+
expect(needsResearch(issue)).toBe(false);
|
|
132
|
+
});
|
|
133
|
+
it('respects custom delay threshold', () => {
|
|
134
|
+
const config = {
|
|
135
|
+
...DEFAULT_TOP_OF_FUNNEL_CONFIG,
|
|
136
|
+
iceboxResearchDelayMs: 10 * 60 * 1000, // 10 minutes
|
|
137
|
+
};
|
|
138
|
+
// Created 15 minutes ago — should pass reduced threshold
|
|
139
|
+
const issue = makeIssue({
|
|
140
|
+
createdAt: Date.now() - 15 * 60 * 1000,
|
|
141
|
+
description: sparseDescription(),
|
|
142
|
+
});
|
|
143
|
+
expect(needsResearch(issue, config)).toBe(true);
|
|
144
|
+
});
|
|
145
|
+
it('respects custom research request labels', () => {
|
|
146
|
+
const config = {
|
|
147
|
+
...DEFAULT_TOP_OF_FUNNEL_CONFIG,
|
|
148
|
+
researchRequestLabels: ['Research Me'],
|
|
149
|
+
};
|
|
150
|
+
const issue = makeIssue({
|
|
151
|
+
description: wellResearchedDescription(),
|
|
152
|
+
labels: ['Research Me'],
|
|
153
|
+
});
|
|
154
|
+
expect(needsResearch(issue, config)).toBe(true);
|
|
155
|
+
// Default label should NOT trigger with custom config
|
|
156
|
+
const issueDefault = makeIssue({
|
|
157
|
+
description: wellResearchedDescription(),
|
|
158
|
+
labels: ['Needs Research'],
|
|
159
|
+
});
|
|
160
|
+
expect(needsResearch(issueDefault, config)).toBe(false);
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
// ---------------------------------------------------------------------------
|
|
164
|
+
// isReadyForBacklogCreation
|
|
165
|
+
// ---------------------------------------------------------------------------
|
|
166
|
+
describe('isReadyForBacklogCreation', () => {
|
|
167
|
+
it('returns false if issue is not in Icebox', () => {
|
|
168
|
+
const issue = makeIssue({
|
|
169
|
+
status: 'Backlog',
|
|
170
|
+
description: wellResearchedDescription(),
|
|
171
|
+
});
|
|
172
|
+
expect(isReadyForBacklogCreation(issue)).toBe(false);
|
|
173
|
+
});
|
|
174
|
+
it('returns false for sparse description', () => {
|
|
175
|
+
const issue = makeIssue({ description: sparseDescription() });
|
|
176
|
+
expect(isReadyForBacklogCreation(issue)).toBe(false);
|
|
177
|
+
});
|
|
178
|
+
it('returns true for well-researched Icebox issue', () => {
|
|
179
|
+
const issue = makeIssue({ description: wellResearchedDescription() });
|
|
180
|
+
expect(isReadyForBacklogCreation(issue)).toBe(true);
|
|
181
|
+
});
|
|
182
|
+
it('returns false for parent issues', () => {
|
|
183
|
+
const issue = makeIssue({
|
|
184
|
+
parentId: 'parent-1',
|
|
185
|
+
description: wellResearchedDescription(),
|
|
186
|
+
});
|
|
187
|
+
expect(isReadyForBacklogCreation(issue)).toBe(false);
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
// ---------------------------------------------------------------------------
|
|
191
|
+
// determineTopOfFunnelAction
|
|
192
|
+
// ---------------------------------------------------------------------------
|
|
193
|
+
describe('determineTopOfFunnelAction', () => {
|
|
194
|
+
// Enable both auto-triggers for testing (defaults are off)
|
|
195
|
+
const config = {
|
|
196
|
+
...DEFAULT_TOP_OF_FUNNEL_CONFIG,
|
|
197
|
+
enableAutoResearch: true,
|
|
198
|
+
enableAutoBacklogCreation: true,
|
|
199
|
+
};
|
|
200
|
+
beforeEach(() => {
|
|
201
|
+
vi.useFakeTimers();
|
|
202
|
+
vi.setSystemTime(new Date('2025-06-01T12:00:00Z'));
|
|
203
|
+
});
|
|
204
|
+
afterEach(() => {
|
|
205
|
+
vi.useRealTimers();
|
|
206
|
+
});
|
|
207
|
+
// -- Guard rails --
|
|
208
|
+
it('returns none when issue is not in Icebox', () => {
|
|
209
|
+
const issue = makeIssue({ status: 'Backlog' });
|
|
210
|
+
const action = determineTopOfFunnelAction(issue, config, defaultContext());
|
|
211
|
+
expect(action.type).toBe('none');
|
|
212
|
+
expect(action.reason).toContain('not in Icebox');
|
|
213
|
+
});
|
|
214
|
+
it('returns none when issue has an active session', () => {
|
|
215
|
+
const issue = makeIssue();
|
|
216
|
+
const action = determineTopOfFunnelAction(issue, config, defaultContext({ hasActiveSession: true }));
|
|
217
|
+
expect(action.type).toBe('none');
|
|
218
|
+
expect(action.reason).toContain('active agent session');
|
|
219
|
+
});
|
|
220
|
+
it('returns none when issue is held', () => {
|
|
221
|
+
const issue = makeIssue();
|
|
222
|
+
const action = determineTopOfFunnelAction(issue, config, defaultContext({ isHeld: true }));
|
|
223
|
+
expect(action.type).toBe('none');
|
|
224
|
+
expect(action.reason).toContain('held');
|
|
225
|
+
});
|
|
226
|
+
it('returns none for parent/coordinator issues', () => {
|
|
227
|
+
const issue = makeIssue();
|
|
228
|
+
const action = determineTopOfFunnelAction(issue, config, defaultContext({ isParentIssue: true }));
|
|
229
|
+
expect(action.type).toBe('none');
|
|
230
|
+
expect(action.reason).toContain('coordination');
|
|
231
|
+
});
|
|
232
|
+
// -- Research triggers --
|
|
233
|
+
it('triggers research for sparse description after delay', () => {
|
|
234
|
+
const issue = makeIssue({ description: sparseDescription() });
|
|
235
|
+
const action = determineTopOfFunnelAction(issue, config, defaultContext());
|
|
236
|
+
expect(action.type).toBe('trigger-research');
|
|
237
|
+
expect(action.reason).toContain('lacks sufficient detail');
|
|
238
|
+
});
|
|
239
|
+
it('triggers research when research label is present', () => {
|
|
240
|
+
const issue = makeIssue({
|
|
241
|
+
description: wellResearchedDescription(),
|
|
242
|
+
labels: ['Needs Research'],
|
|
243
|
+
});
|
|
244
|
+
const action = determineTopOfFunnelAction(issue, config, defaultContext());
|
|
245
|
+
expect(action.type).toBe('trigger-research');
|
|
246
|
+
expect(action.reason).toContain('research-request label');
|
|
247
|
+
});
|
|
248
|
+
it('returns none when research is needed but delay not met', () => {
|
|
249
|
+
const issue = makeIssue({
|
|
250
|
+
description: sparseDescription(),
|
|
251
|
+
createdAt: Date.now() - 30 * 60 * 1000, // 30 min
|
|
252
|
+
});
|
|
253
|
+
const action = determineTopOfFunnelAction(issue, config, defaultContext());
|
|
254
|
+
expect(action.type).toBe('none');
|
|
255
|
+
expect(action.reason).toContain('not been in Icebox long enough');
|
|
256
|
+
});
|
|
257
|
+
it('skips research when research already completed', () => {
|
|
258
|
+
const issue = makeIssue({ description: sparseDescription() });
|
|
259
|
+
const action = determineTopOfFunnelAction(issue, config, defaultContext({ researchCompleted: true }));
|
|
260
|
+
// Research completed but description still sparse — should not trigger
|
|
261
|
+
// backlog creation either because description is not well-researched
|
|
262
|
+
expect(action.type).toBe('none');
|
|
263
|
+
});
|
|
264
|
+
// -- Backlog-creation triggers --
|
|
265
|
+
it('triggers backlog creation for well-researched issue', () => {
|
|
266
|
+
const issue = makeIssue({ description: wellResearchedDescription() });
|
|
267
|
+
const action = determineTopOfFunnelAction(issue, config, defaultContext());
|
|
268
|
+
expect(action.type).toBe('trigger-backlog-creation');
|
|
269
|
+
expect(action.reason).toContain('ready for backlog creation');
|
|
270
|
+
});
|
|
271
|
+
it('triggers backlog creation when research completed and description is now rich', () => {
|
|
272
|
+
const issue = makeIssue({ description: wellResearchedDescription() });
|
|
273
|
+
const action = determineTopOfFunnelAction(issue, config, defaultContext({ researchCompleted: true }));
|
|
274
|
+
expect(action.type).toBe('trigger-backlog-creation');
|
|
275
|
+
});
|
|
276
|
+
it('skips backlog creation when already completed', () => {
|
|
277
|
+
const issue = makeIssue({ description: wellResearchedDescription() });
|
|
278
|
+
const action = determineTopOfFunnelAction(issue, config, defaultContext({ backlogCreationCompleted: true }));
|
|
279
|
+
expect(action.type).toBe('none');
|
|
280
|
+
expect(action.reason).toContain('already completed');
|
|
281
|
+
});
|
|
282
|
+
it('returns none when both phases completed', () => {
|
|
283
|
+
const issue = makeIssue({ description: wellResearchedDescription() });
|
|
284
|
+
const action = determineTopOfFunnelAction(issue, config, defaultContext({ researchCompleted: true, backlogCreationCompleted: true }));
|
|
285
|
+
expect(action.type).toBe('none');
|
|
286
|
+
expect(action.reason).toContain('already completed');
|
|
287
|
+
});
|
|
288
|
+
// -- Disabled flags --
|
|
289
|
+
it('skips research when enableAutoResearch is false', () => {
|
|
290
|
+
const disabledConfig = {
|
|
291
|
+
...config,
|
|
292
|
+
enableAutoResearch: false,
|
|
293
|
+
};
|
|
294
|
+
const issue = makeIssue({ description: sparseDescription() });
|
|
295
|
+
const action = determineTopOfFunnelAction(issue, disabledConfig, defaultContext());
|
|
296
|
+
expect(action.type).toBe('none');
|
|
297
|
+
expect(action.reason).toContain('not well-researched');
|
|
298
|
+
});
|
|
299
|
+
it('skips backlog creation when enableAutoBacklogCreation is false', () => {
|
|
300
|
+
const disabledConfig = {
|
|
301
|
+
...config,
|
|
302
|
+
enableAutoBacklogCreation: false,
|
|
303
|
+
};
|
|
304
|
+
const issue = makeIssue({ description: wellResearchedDescription() });
|
|
305
|
+
const action = determineTopOfFunnelAction(issue, disabledConfig, defaultContext());
|
|
306
|
+
expect(action.type).toBe('none');
|
|
307
|
+
expect(action.reason).toContain('disabled');
|
|
308
|
+
});
|
|
309
|
+
it('still triggers research even when backlog creation is disabled', () => {
|
|
310
|
+
const disabledConfig = {
|
|
311
|
+
...config,
|
|
312
|
+
enableAutoBacklogCreation: false,
|
|
313
|
+
};
|
|
314
|
+
const issue = makeIssue({ description: sparseDescription() });
|
|
315
|
+
const action = determineTopOfFunnelAction(issue, disabledConfig, defaultContext());
|
|
316
|
+
expect(action.type).toBe('trigger-research');
|
|
317
|
+
});
|
|
318
|
+
// -- Config override combos --
|
|
319
|
+
it('respects a custom iceboxResearchDelayMs', () => {
|
|
320
|
+
const customConfig = {
|
|
321
|
+
...config,
|
|
322
|
+
iceboxResearchDelayMs: 5 * 60 * 1000, // 5 minutes
|
|
323
|
+
};
|
|
324
|
+
const issue = makeIssue({
|
|
325
|
+
description: sparseDescription(),
|
|
326
|
+
createdAt: Date.now() - 10 * 60 * 1000, // 10 min
|
|
327
|
+
});
|
|
328
|
+
const action = determineTopOfFunnelAction(issue, customConfig, defaultContext());
|
|
329
|
+
expect(action.type).toBe('trigger-research');
|
|
330
|
+
});
|
|
331
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export * from './orchestrator/index.js';
|
|
2
|
+
export * from './providers/index.js';
|
|
3
|
+
export * from './logger.js';
|
|
4
|
+
export * from './deployment/index.js';
|
|
5
|
+
export * from './templates/index.js';
|
|
6
|
+
export * from './governor/index.js';
|
|
7
|
+
export * from './frontend/index.js';
|
|
8
|
+
export * from './config/index.js';
|
|
9
|
+
export * from './manifest/index.js';
|
|
10
|
+
export * from './tools/index.js';
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,yBAAyB,CAAA;AACvC,cAAc,sBAAsB,CAAA;AACpC,cAAc,aAAa,CAAA;AAC3B,cAAc,uBAAuB,CAAA;AACrC,cAAc,sBAAsB,CAAA;AACpC,cAAc,qBAAqB,CAAA;AACnC,cAAc,qBAAqB,CAAA;AACnC,cAAc,mBAAmB,CAAA;AACjC,cAAc,qBAAqB,CAAA;AACnC,cAAc,kBAAkB,CAAA"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export * from './orchestrator/index.js';
|
|
2
|
+
export * from './providers/index.js';
|
|
3
|
+
export * from './logger.js';
|
|
4
|
+
export * from './deployment/index.js';
|
|
5
|
+
export * from './templates/index.js';
|
|
6
|
+
export * from './governor/index.js';
|
|
7
|
+
export * from './frontend/index.js';
|
|
8
|
+
export * from './config/index.js';
|
|
9
|
+
export * from './manifest/index.js';
|
|
10
|
+
export * from './tools/index.js';
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @deprecated Use `af-linear` from `@renseiai/agentfactory-cli` instead.
|
|
4
|
+
* This file is kept for backwards compatibility. The canonical implementation
|
|
5
|
+
* is in packages/cli/src/lib/linear-runner.ts.
|
|
6
|
+
*
|
|
7
|
+
* Linear CLI - Command-line interface for Linear Agent SDK
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* pnpm af-linear <command> [options]
|
|
11
|
+
*
|
|
12
|
+
* Commands:
|
|
13
|
+
* get-issue <id> Get issue details
|
|
14
|
+
* create-issue Create a new issue
|
|
15
|
+
* update-issue <id> Update an existing issue
|
|
16
|
+
* list-comments <issueId> List comments on an issue
|
|
17
|
+
* create-comment <issueId> Create a comment on an issue
|
|
18
|
+
* list-backlog-issues List backlog issues for a project
|
|
19
|
+
* list-unblocked-backlog List unblocked backlog issues
|
|
20
|
+
* check-blocked <id> Check if an issue is blocked
|
|
21
|
+
* add-relation <id> <id> Create relation between issues
|
|
22
|
+
* list-relations <id> List relations for an issue
|
|
23
|
+
* remove-relation <id> Remove a relation by ID
|
|
24
|
+
* list-sub-issues <id> List sub-issues of a parent issue
|
|
25
|
+
* list-sub-issue-statuses <id> List sub-issue statuses (lightweight)
|
|
26
|
+
* update-sub-issue <id> Update sub-issue status with comment
|
|
27
|
+
* check-deployment <PR> Check Vercel deployment status for a PR
|
|
28
|
+
*
|
|
29
|
+
* Array Values:
|
|
30
|
+
* --labels accepts comma-separated: --labels "Bug,Feature"
|
|
31
|
+
* For values with commas, use JSON: --labels '["Bug", "UI, UX"]'
|
|
32
|
+
* Text fields (--description, --title, --body) preserve commas.
|
|
33
|
+
*
|
|
34
|
+
* Environment:
|
|
35
|
+
* LINEAR_API_KEY Required API key for authentication
|
|
36
|
+
*/
|
|
37
|
+
import 'dotenv/config';
|
|
38
|
+
//# sourceMappingURL=linear-cli.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"linear-cli.d.ts","sourceRoot":"","sources":["../../src/linear-cli.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAGH,OAAO,eAAe,CAAA"}
|