@iservu-inc/adf-cli 0.17.1 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/.project/chats/current/SESSION-STATUS.md +29 -27
  2. package/.project/docs/ROADMAP.md +74 -64
  3. package/CHANGELOG.md +78 -0
  4. package/CLAUDE.md +1 -1
  5. package/README.md +63 -27
  6. package/bin/adf.js +54 -0
  7. package/lib/analysis/dynamic-pipeline.js +26 -0
  8. package/lib/analysis/knowledge-graph.js +66 -0
  9. package/lib/commands/deploy.js +35 -0
  10. package/lib/commands/harness.js +345 -0
  11. package/lib/commands/init.js +135 -10
  12. package/lib/frameworks/interviewer.js +130 -0
  13. package/lib/frameworks/progress-tracker.js +30 -1
  14. package/lib/frameworks/session-manager.js +76 -0
  15. package/lib/harness/context-window-manager.js +255 -0
  16. package/lib/harness/event-logger.js +115 -0
  17. package/lib/harness/feature-manifest.js +175 -0
  18. package/lib/harness/headless-adapter.js +184 -0
  19. package/lib/harness/milestone-tracker.js +183 -0
  20. package/lib/harness/protocol.js +503 -0
  21. package/lib/harness/provider-bridge.js +226 -0
  22. package/lib/harness/run-manager.js +267 -0
  23. package/lib/templates/scripts/analyze-docs.js +12 -1
  24. package/lib/utils/context-extractor.js +48 -0
  25. package/lib/utils/framework-detector.js +10 -1
  26. package/lib/utils/project-detector.js +5 -1
  27. package/lib/utils/tool-detector.js +167 -0
  28. package/lib/utils/tool-feature-registry.js +82 -13
  29. package/lib/utils/tool-recommender.js +325 -0
  30. package/package.json +1 -1
  31. package/tests/context-extractor.test.js +45 -0
  32. package/tests/framework-detector.test.js +28 -0
  33. package/tests/harness-backward-compat.test.js +251 -0
  34. package/tests/harness-context-window.test.js +310 -0
  35. package/tests/harness-event-logger.test.js +148 -0
  36. package/tests/harness-feature-manifest.test.js +124 -0
  37. package/tests/harness-headless-adapter.test.js +196 -0
  38. package/tests/harness-integration.test.js +207 -0
  39. package/tests/harness-milestone-tracker.test.js +158 -0
  40. package/tests/harness-protocol.test.js +341 -0
  41. package/tests/harness-provider-bridge.test.js +180 -0
  42. package/tests/harness-provider-switch.test.js +204 -0
  43. package/tests/harness-run-manager.test.js +131 -0
  44. package/tests/tool-detector.test.js +152 -0
  45. package/tests/tool-recommender.test.js +218 -0
@@ -0,0 +1,218 @@
1
+ const ToolRecommender = require('../lib/utils/tool-recommender');
2
+
3
+ describe('ToolRecommender', () => {
4
+ function makeRecommender(overrides = {}) {
5
+ return new ToolRecommender({
6
+ workflow: 'balanced',
7
+ projectType: 'existing',
8
+ workflowContext: { teamSize: 'small', complexity: 'moderate', stage: 'active' },
9
+ installedTools: new Map(),
10
+ ...overrides
11
+ });
12
+ }
13
+
14
+ describe('scoreTool', () => {
15
+ test('should return scores for a valid tool', () => {
16
+ const rec = makeRecommender();
17
+ const result = rec.scoreTool('windsurf');
18
+ expect(result).not.toBeNull();
19
+ expect(result.toolId).toBe('windsurf');
20
+ expect(result.name).toBe('Windsurf');
21
+ expect(typeof result.totalScore).toBe('number');
22
+ expect(result.totalScore).toBeGreaterThan(0);
23
+ expect(result.totalScore).toBeLessThanOrEqual(110); // max possible with bonuses
24
+ });
25
+
26
+ test('should return null for unknown tool', () => {
27
+ const rec = makeRecommender();
28
+ expect(rec.scoreTool('nonexistent')).toBeNull();
29
+ });
30
+
31
+ test('all tools should have scores in valid range', () => {
32
+ const rec = makeRecommender();
33
+ const toolIds = ['antigravity', 'zed', 'claude-code', 'gemini-cli', 'opencode',
34
+ 'deepagent', 'windsurf', 'cursor', 'vscode', 'codex-cli', 'kiro', 'trae'];
35
+ for (const id of toolIds) {
36
+ const result = rec.scoreTool(id);
37
+ expect(result).not.toBeNull();
38
+ expect(result.totalScore).toBeGreaterThan(0);
39
+ expect(result.totalScore).toBeLessThanOrEqual(110);
40
+ }
41
+ });
42
+
43
+ test('should have six scoring dimensions', () => {
44
+ const rec = makeRecommender();
45
+ const result = rec.scoreTool('windsurf');
46
+ expect(result.scores).toHaveProperty('automation');
47
+ expect(result.scores).toHaveProperty('skillIntegration');
48
+ expect(result.scores).toHaveProperty('orchestration');
49
+ expect(result.scores).toHaveProperty('generatorMaturity');
50
+ expect(result.scores).toHaveProperty('workflowAlignment');
51
+ expect(result.scores).toHaveProperty('modelStratification');
52
+ });
53
+ });
54
+
55
+ describe('workflow alignment', () => {
56
+ test('comprehensive workflow should favor agent-first tools (tier 1)', () => {
57
+ const rec = makeRecommender({ workflow: 'comprehensive' });
58
+ const antigravity = rec.scoreTool('antigravity');
59
+ const trae = rec.scoreTool('trae');
60
+ // Antigravity (tier 1) should get higher workflow alignment than Trae (tier 3)
61
+ expect(antigravity.scores.workflowAlignment).toBeGreaterThan(trae.scores.workflowAlignment);
62
+ });
63
+
64
+ test('rapid workflow should favor CLI tools', () => {
65
+ const rec = makeRecommender({ workflow: 'rapid' });
66
+ const claudeCode = rec.scoreTool('claude-code');
67
+ const windsurf = rec.scoreTool('windsurf');
68
+ // CLI tool should get higher workflow alignment for rapid
69
+ expect(claudeCode.scores.workflowAlignment).toBeGreaterThan(windsurf.scores.workflowAlignment);
70
+ });
71
+
72
+ test('balanced workflow should favor established IDEs', () => {
73
+ const rec = makeRecommender({ workflow: 'balanced' });
74
+ const windsurf = rec.scoreTool('windsurf');
75
+ const codexCli = rec.scoreTool('codex-cli');
76
+ // Windsurf (tier 3 IDE) should get higher workflow alignment for balanced
77
+ expect(windsurf.scores.workflowAlignment).toBeGreaterThan(codexCli.scores.workflowAlignment);
78
+ });
79
+ });
80
+
81
+ describe('installation bonus', () => {
82
+ test('should add bonus for installed tools', () => {
83
+ const installed = new Map([['windsurf', { installed: true, method: 'binary', path: '/usr/bin/windsurf' }]]);
84
+ const recInstalled = makeRecommender({ installedTools: installed });
85
+ const recNotInstalled = makeRecommender();
86
+
87
+ const withBonus = recInstalled.scoreTool('windsurf');
88
+ const withoutBonus = recNotInstalled.scoreTool('windsurf');
89
+
90
+ expect(withBonus.totalScore).toBe(withoutBonus.totalScore + 5);
91
+ expect(withBonus.installed).toBe(true);
92
+ expect(withoutBonus.installed).toBe(false);
93
+ });
94
+ });
95
+
96
+ describe('context modifier', () => {
97
+ test('large team should boost IDE tools', () => {
98
+ const recLarge = makeRecommender({
99
+ workflowContext: { teamSize: 'large', complexity: 'moderate', stage: 'active' }
100
+ });
101
+ const recSolo = makeRecommender({
102
+ workflowContext: { teamSize: 'solo', complexity: 'moderate', stage: 'active' }
103
+ });
104
+
105
+ const largeWindsurf = recLarge.scoreTool('windsurf');
106
+ const soloWindsurf = recSolo.scoreTool('windsurf');
107
+ expect(largeWindsurf.contextMod).toBeGreaterThan(soloWindsurf.contextMod);
108
+ });
109
+
110
+ test('complex projects should boost low-tier tools', () => {
111
+ const recComplex = makeRecommender({
112
+ workflowContext: { teamSize: 'small', complexity: 'very-complex', stage: 'active' }
113
+ });
114
+ const recSimple = makeRecommender({
115
+ workflowContext: { teamSize: 'small', complexity: 'simple', stage: 'active' }
116
+ });
117
+
118
+ const complexClaude = recComplex.scoreTool('claude-code');
119
+ const simpleClaude = recSimple.scoreTool('claude-code');
120
+ expect(complexClaude.contextMod).toBeGreaterThan(simpleClaude.contextMod);
121
+ });
122
+ });
123
+
124
+ describe('recommend', () => {
125
+ test('should return primary, complementary, ranked, and pairings', () => {
126
+ const rec = makeRecommender();
127
+ const result = rec.recommend();
128
+
129
+ expect(result).toHaveProperty('primary');
130
+ expect(result).toHaveProperty('complementary');
131
+ expect(result).toHaveProperty('ranked');
132
+ expect(result).toHaveProperty('pairings');
133
+
134
+ expect(result.primary).not.toBeNull();
135
+ expect(result.primary.recommended).toBe(true);
136
+ expect(result.primary.reason).toBeTruthy();
137
+ });
138
+
139
+ test('complementary tools should be from different category than primary', () => {
140
+ const rec = makeRecommender();
141
+ const result = rec.recommend();
142
+
143
+ if (result.complementary.length > 0) {
144
+ const ToolFeatureRegistry = require('../lib/utils/tool-feature-registry');
145
+ const primaryConfig = ToolFeatureRegistry.getDetectionConfig(result.primary.toolId);
146
+ for (const comp of result.complementary) {
147
+ const compConfig = ToolFeatureRegistry.getDetectionConfig(comp.toolId);
148
+ expect(compConfig.category).not.toBe(primaryConfig.category);
149
+ }
150
+ }
151
+ });
152
+
153
+ test('ranked list should be sorted by totalScore descending', () => {
154
+ const rec = makeRecommender();
155
+ const result = rec.recommend();
156
+
157
+ for (let i = 1; i < result.ranked.length; i++) {
158
+ expect(result.ranked[i - 1].totalScore).toBeGreaterThanOrEqual(result.ranked[i].totalScore);
159
+ }
160
+ });
161
+
162
+ test('ranked list should include all scored tools', () => {
163
+ const rec = makeRecommender();
164
+ const result = rec.recommend();
165
+ expect(result.ranked.length).toBe(12); // 12 tools with base scores
166
+ });
167
+
168
+ test('complementary should have max 2 entries', () => {
169
+ const rec = makeRecommender();
170
+ const result = rec.recommend();
171
+ expect(result.complementary.length).toBeLessThanOrEqual(2);
172
+ });
173
+
174
+ test('pairings should have max 3 entries', () => {
175
+ const rec = makeRecommender();
176
+ const result = rec.recommend();
177
+ expect(result.pairings.length).toBeLessThanOrEqual(3);
178
+ });
179
+ });
180
+
181
+ describe('formatForDisplay', () => {
182
+ test('should produce formatted string output', () => {
183
+ const rec = makeRecommender();
184
+ const result = rec.recommend();
185
+ const display = ToolRecommender.formatForDisplay(result);
186
+
187
+ expect(typeof display).toBe('string');
188
+ expect(display.length).toBeGreaterThan(0);
189
+ // Should contain the primary tool name
190
+ expect(display).toContain(result.primary.name);
191
+ });
192
+
193
+ test('should handle null recommendation', () => {
194
+ const display = ToolRecommender.formatForDisplay(null);
195
+ expect(display).toContain('No tool recommendations');
196
+ });
197
+
198
+ test('should handle recommendation with no primary', () => {
199
+ const display = ToolRecommender.formatForDisplay({ primary: null, complementary: [], ranked: [], pairings: [] });
200
+ expect(display).toContain('No tool recommendations');
201
+ });
202
+
203
+ test('should show install status indicators', () => {
204
+ // Mark the primary recommended tool as installed so [Installed] appears in the Primary line
205
+ const rec = makeRecommender();
206
+ const preResult = rec.recommend();
207
+ const primaryId = preResult.primary.toolId;
208
+
209
+ const installed = new Map([[primaryId, { installed: true, method: 'binary', path: '/usr/bin/test' }]]);
210
+ const rec2 = makeRecommender({ installedTools: installed });
211
+ const result = rec2.recommend();
212
+ const display = ToolRecommender.formatForDisplay(result);
213
+
214
+ // Primary line should show [Installed]
215
+ expect(display).toContain('Installed');
216
+ });
217
+ });
218
+ });