@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.
- package/.project/chats/current/SESSION-STATUS.md +29 -27
- package/.project/docs/ROADMAP.md +74 -64
- package/CHANGELOG.md +78 -0
- package/CLAUDE.md +1 -1
- package/README.md +63 -27
- package/bin/adf.js +54 -0
- package/lib/analysis/dynamic-pipeline.js +26 -0
- package/lib/analysis/knowledge-graph.js +66 -0
- package/lib/commands/deploy.js +35 -0
- package/lib/commands/harness.js +345 -0
- package/lib/commands/init.js +135 -10
- package/lib/frameworks/interviewer.js +130 -0
- package/lib/frameworks/progress-tracker.js +30 -1
- package/lib/frameworks/session-manager.js +76 -0
- package/lib/harness/context-window-manager.js +255 -0
- package/lib/harness/event-logger.js +115 -0
- package/lib/harness/feature-manifest.js +175 -0
- package/lib/harness/headless-adapter.js +184 -0
- package/lib/harness/milestone-tracker.js +183 -0
- package/lib/harness/protocol.js +503 -0
- package/lib/harness/provider-bridge.js +226 -0
- package/lib/harness/run-manager.js +267 -0
- package/lib/templates/scripts/analyze-docs.js +12 -1
- package/lib/utils/context-extractor.js +48 -0
- package/lib/utils/framework-detector.js +10 -1
- package/lib/utils/project-detector.js +5 -1
- package/lib/utils/tool-detector.js +167 -0
- package/lib/utils/tool-feature-registry.js +82 -13
- package/lib/utils/tool-recommender.js +325 -0
- package/package.json +1 -1
- package/tests/context-extractor.test.js +45 -0
- package/tests/framework-detector.test.js +28 -0
- package/tests/harness-backward-compat.test.js +251 -0
- package/tests/harness-context-window.test.js +310 -0
- package/tests/harness-event-logger.test.js +148 -0
- package/tests/harness-feature-manifest.test.js +124 -0
- package/tests/harness-headless-adapter.test.js +196 -0
- package/tests/harness-integration.test.js +207 -0
- package/tests/harness-milestone-tracker.test.js +158 -0
- package/tests/harness-protocol.test.js +341 -0
- package/tests/harness-provider-bridge.test.js +180 -0
- package/tests/harness-provider-switch.test.js +204 -0
- package/tests/harness-run-manager.test.js +131 -0
- package/tests/tool-detector.test.js +152 -0
- package/tests/tool-recommender.test.js +218 -0
|
@@ -216,6 +216,72 @@ class KnowledgeGraph {
|
|
|
216
216
|
};
|
|
217
217
|
}
|
|
218
218
|
|
|
219
|
+
/**
|
|
220
|
+
* Export high-confidence items for handoff package (compact format)
|
|
221
|
+
*/
|
|
222
|
+
toHandoffFormat(minConfidence = 70, maxContentLength = 200) {
|
|
223
|
+
const highConfidenceItems = [];
|
|
224
|
+
const typesSeen = [];
|
|
225
|
+
|
|
226
|
+
for (const [type, items] of this.knowledge.entries()) {
|
|
227
|
+
typesSeen.push(type);
|
|
228
|
+
for (const item of items) {
|
|
229
|
+
if (item.confidence >= minConfidence) {
|
|
230
|
+
highConfidenceItems.push({
|
|
231
|
+
type,
|
|
232
|
+
content: item.content.length > maxContentLength
|
|
233
|
+
? item.content.slice(0, maxContentLength) + '...'
|
|
234
|
+
: item.content,
|
|
235
|
+
confidence: item.confidence,
|
|
236
|
+
sources: item.sources
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return {
|
|
243
|
+
highConfidenceItems,
|
|
244
|
+
totalItems: this.getStats().totalItems,
|
|
245
|
+
typesSeen
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Restore knowledge from handoff format
|
|
251
|
+
*/
|
|
252
|
+
fromHandoffFormat(data) {
|
|
253
|
+
if (!data || !data.highConfidenceItems) return;
|
|
254
|
+
|
|
255
|
+
for (const item of data.highConfidenceItems) {
|
|
256
|
+
this.add([{
|
|
257
|
+
type: item.type,
|
|
258
|
+
content: item.content,
|
|
259
|
+
confidence: item.confidence,
|
|
260
|
+
source: item.sources ? item.sources[0] : 'handoff'
|
|
261
|
+
}]);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Get high-confidence snapshot (items at confidence >= 80)
|
|
267
|
+
*/
|
|
268
|
+
getHighConfidenceSnapshot() {
|
|
269
|
+
const snapshot = {};
|
|
270
|
+
|
|
271
|
+
for (const [type, items] of this.knowledge.entries()) {
|
|
272
|
+
const highConf = items.filter(i => i.confidence >= 80);
|
|
273
|
+
if (highConf.length > 0) {
|
|
274
|
+
snapshot[type] = highConf.map(i => ({
|
|
275
|
+
content: i.content,
|
|
276
|
+
confidence: i.confidence,
|
|
277
|
+
sources: i.sources
|
|
278
|
+
}));
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return snapshot;
|
|
283
|
+
}
|
|
284
|
+
|
|
219
285
|
/**
|
|
220
286
|
* Clear all knowledge
|
|
221
287
|
*/
|
package/lib/commands/deploy.js
CHANGED
|
@@ -2,6 +2,8 @@ const fs = require('fs-extra');
|
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const chalk = require('chalk');
|
|
4
4
|
const ora = require('ora');
|
|
5
|
+
const ToolDetector = require('../utils/tool-detector');
|
|
6
|
+
const ToolRecommender = require('../utils/tool-recommender');
|
|
5
7
|
const {
|
|
6
8
|
generateAgentsMd,
|
|
7
9
|
generateWindsurf,
|
|
@@ -334,6 +336,39 @@ function getAgentsForWorkflow(workflow) {
|
|
|
334
336
|
}
|
|
335
337
|
|
|
336
338
|
async function deploy(tool, options) {
|
|
339
|
+
// Show tool recommendations
|
|
340
|
+
if (options.recommend) {
|
|
341
|
+
const cwd = process.cwd();
|
|
342
|
+
|
|
343
|
+
const spinner = ora('Analyzing project and detecting tools...').start();
|
|
344
|
+
let installedTools;
|
|
345
|
+
try {
|
|
346
|
+
installedTools = await ToolDetector.detectAll(cwd);
|
|
347
|
+
spinner.succeed('Analysis complete');
|
|
348
|
+
} catch {
|
|
349
|
+
installedTools = new Map();
|
|
350
|
+
spinner.warn('Could not detect tools');
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Determine workflow from latest session if available
|
|
354
|
+
let workflow = 'balanced';
|
|
355
|
+
const sessionPath = await findLatestSession(cwd);
|
|
356
|
+
if (sessionPath) {
|
|
357
|
+
workflow = await getFrameworkFromSession(sessionPath);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const recommender = new ToolRecommender({
|
|
361
|
+
workflow,
|
|
362
|
+
projectType: 'existing',
|
|
363
|
+
workflowContext: {},
|
|
364
|
+
installedTools
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
const recommendation = recommender.recommend();
|
|
368
|
+
console.log(ToolRecommender.formatForDisplay(recommendation));
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
|
|
337
372
|
// List tools
|
|
338
373
|
if (options.list) {
|
|
339
374
|
console.log(chalk.cyan.bold('\n📋 Available Deployment Tools:\n'));
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
const ora = require('ora');
|
|
5
|
+
const RunManager = require('../harness/run-manager');
|
|
6
|
+
const ContextWindowManager = require('../harness/context-window-manager');
|
|
7
|
+
const MilestoneTracker = require('../harness/milestone-tracker');
|
|
8
|
+
const FeatureManifestManager = require('../harness/feature-manifest');
|
|
9
|
+
const EventLogger = require('../harness/event-logger');
|
|
10
|
+
const ProviderBridge = require('../harness/provider-bridge');
|
|
11
|
+
const HeadlessAdapter = require('../harness/headless-adapter');
|
|
12
|
+
const { getEnvFilePath, loadEnvIntoProcess, configureAIProvider } = require('../ai/ai-config');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Harness command handlers
|
|
16
|
+
*
|
|
17
|
+
* Manages long-running AI agent sessions across context windows.
|
|
18
|
+
* Commands: start, resume, status, handoff, events, manifest
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
async function harnessStart(options) {
|
|
22
|
+
const cwd = process.cwd();
|
|
23
|
+
|
|
24
|
+
console.log(chalk.cyan.bold('\n🔗 Harness Engineering Protocol\n'));
|
|
25
|
+
|
|
26
|
+
// Load env
|
|
27
|
+
const envPath = getEnvFilePath(cwd);
|
|
28
|
+
if (await fs.pathExists(envPath)) {
|
|
29
|
+
loadEnvIntoProcess(envPath);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const manager = new RunManager(cwd);
|
|
33
|
+
|
|
34
|
+
// Check for existing active run
|
|
35
|
+
const existingRun = await manager.getCurrentRun();
|
|
36
|
+
if (existingRun && existingRun.status === 'running') {
|
|
37
|
+
console.log(chalk.yellow(`Active run found: ${existingRun.id}`));
|
|
38
|
+
console.log(chalk.yellow(`Status: ${existingRun.status} | Workflow: ${existingRun.workflow}\n`));
|
|
39
|
+
console.log(chalk.gray('Use "adf harness resume" to continue or "adf harness status" for details.\n'));
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Determine workflow
|
|
44
|
+
const workflow = options.workflow || 'balanced';
|
|
45
|
+
const mode = options.headless ? 'headless' : 'interactive';
|
|
46
|
+
|
|
47
|
+
// Configure AI provider
|
|
48
|
+
let aiConfig = null;
|
|
49
|
+
if (!options.headless) {
|
|
50
|
+
aiConfig = await configureAIProvider(cwd);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Create run
|
|
54
|
+
const run = await manager.createRun({
|
|
55
|
+
workflow,
|
|
56
|
+
mode,
|
|
57
|
+
spec: {
|
|
58
|
+
goals: options.goals ? options.goals.split(',') : [],
|
|
59
|
+
nonGoals: [],
|
|
60
|
+
constraints: [],
|
|
61
|
+
doneWhen: []
|
|
62
|
+
},
|
|
63
|
+
provider: aiConfig ? {
|
|
64
|
+
id: aiConfig.provider,
|
|
65
|
+
model: aiConfig.model,
|
|
66
|
+
contextWindowSize: null
|
|
67
|
+
} : { id: null, model: null, contextWindowSize: null }
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
await manager.startRun(run.id);
|
|
71
|
+
|
|
72
|
+
console.log(chalk.green(`\n✓ Harness run created: ${run.id}`));
|
|
73
|
+
console.log(chalk.gray(` Workflow: ${workflow}`));
|
|
74
|
+
console.log(chalk.gray(` Mode: ${mode}`));
|
|
75
|
+
|
|
76
|
+
// Open first context window
|
|
77
|
+
const cwManager = new ContextWindowManager(manager.getRunDir(run.id), run.id);
|
|
78
|
+
const window = await cwManager.openWindow();
|
|
79
|
+
|
|
80
|
+
console.log(chalk.gray(` Context Window: ${window.id} (sequence ${window.sequence})`));
|
|
81
|
+
console.log(chalk.cyan(`\nRun "adf init --harness --run-id ${run.id}" to start the interview.\n`));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function harnessResume(options) {
|
|
85
|
+
const cwd = process.cwd();
|
|
86
|
+
|
|
87
|
+
console.log(chalk.cyan.bold('\n🔗 Resume Harness Run\n'));
|
|
88
|
+
|
|
89
|
+
const manager = new RunManager(cwd);
|
|
90
|
+
|
|
91
|
+
const runId = options.runId;
|
|
92
|
+
let run;
|
|
93
|
+
|
|
94
|
+
if (runId) {
|
|
95
|
+
run = await manager.loadRun(runId);
|
|
96
|
+
} else {
|
|
97
|
+
run = await manager.getCurrentRun();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (!run) {
|
|
101
|
+
console.log(chalk.yellow('No harness run found to resume.\n'));
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (run.status !== 'paused') {
|
|
106
|
+
console.log(chalk.yellow(`Run ${run.id} is not paused (status: ${run.status}).\n`));
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Resume the run
|
|
111
|
+
await manager.resumeRun(run.id);
|
|
112
|
+
|
|
113
|
+
// Open new context window and consume handoff
|
|
114
|
+
const cwManager = new ContextWindowManager(manager.getRunDir(run.id), run.id);
|
|
115
|
+
const window = await cwManager.openWindow();
|
|
116
|
+
const handoff = await cwManager.consumeHandoffPackage('latest');
|
|
117
|
+
|
|
118
|
+
console.log(chalk.green(`✓ Run resumed: ${run.id}`));
|
|
119
|
+
console.log(chalk.gray(` Context Window: ${window.id} (sequence ${window.sequence})`));
|
|
120
|
+
|
|
121
|
+
if (handoff) {
|
|
122
|
+
console.log(chalk.cyan(`\n📋 Handoff Summary:`));
|
|
123
|
+
console.log(chalk.gray(` ${handoff.progressSummary || 'No summary'}`));
|
|
124
|
+
console.log(chalk.gray(` Completed: ${handoff.completedWork.length} items`));
|
|
125
|
+
console.log(chalk.gray(` Answered: ${handoff.position.answeredIds.length} questions`));
|
|
126
|
+
console.log(chalk.gray(` Remaining: ${handoff.position.remainingIds.length} questions`));
|
|
127
|
+
console.log(chalk.gray(` Knowledge: ${handoff.knowledgeGraph.totalItems} items`));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
console.log(chalk.cyan(`\nRun "adf init --harness --run-id ${run.id}" to continue the interview.\n`));
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async function harnessStatus(options) {
|
|
134
|
+
const cwd = process.cwd();
|
|
135
|
+
|
|
136
|
+
console.log(chalk.cyan.bold('\n🔗 Harness Status\n'));
|
|
137
|
+
|
|
138
|
+
const manager = new RunManager(cwd);
|
|
139
|
+
|
|
140
|
+
if (options.all) {
|
|
141
|
+
const runs = await manager.listRuns();
|
|
142
|
+
if (runs.length === 0) {
|
|
143
|
+
console.log(chalk.gray('No harness runs found.\n'));
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
console.log(chalk.white(`Found ${runs.length} run(s):\n`));
|
|
148
|
+
for (const run of runs) {
|
|
149
|
+
const statusIcon = {
|
|
150
|
+
initializing: '🟡',
|
|
151
|
+
running: '🟢',
|
|
152
|
+
paused: '🟠',
|
|
153
|
+
completed: '✅',
|
|
154
|
+
failed: '❌'
|
|
155
|
+
}[run.status] || '⚪';
|
|
156
|
+
|
|
157
|
+
console.log(` ${statusIcon} ${run.id}`);
|
|
158
|
+
console.log(chalk.gray(` Status: ${run.status} | Workflow: ${run.workflow} | Mode: ${run.mode}`));
|
|
159
|
+
console.log(chalk.gray(` Created: ${new Date(run.createdAt).toLocaleString()}`));
|
|
160
|
+
console.log('');
|
|
161
|
+
}
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Show current run
|
|
166
|
+
const run = await manager.getCurrentRun();
|
|
167
|
+
if (!run) {
|
|
168
|
+
console.log(chalk.gray('No active harness run.\n'));
|
|
169
|
+
console.log(chalk.gray('Use "adf harness start" to create one.\n'));
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
console.log(chalk.white(`Run: ${run.id}`));
|
|
174
|
+
console.log(chalk.gray(`Status: ${run.status}`));
|
|
175
|
+
console.log(chalk.gray(`Workflow: ${run.workflow}`));
|
|
176
|
+
console.log(chalk.gray(`Mode: ${run.mode}`));
|
|
177
|
+
console.log(chalk.gray(`Provider: ${run.provider.id || 'not set'} / ${run.provider.model || 'not set'}`));
|
|
178
|
+
console.log(chalk.gray(`Session: ${run.sessionId || 'not linked'}`));
|
|
179
|
+
console.log(chalk.gray(`Context Windows: ${run.contextWindows.length}`));
|
|
180
|
+
console.log(chalk.gray(`Created: ${new Date(run.createdAt).toLocaleString()}`));
|
|
181
|
+
console.log(chalk.gray(`Updated: ${new Date(run.updatedAt).toLocaleString()}\n`));
|
|
182
|
+
|
|
183
|
+
// Show milestone progress if available
|
|
184
|
+
const milestoneTracker = new MilestoneTracker(manager.getRunDir(run.id));
|
|
185
|
+
if (await milestoneTracker.load()) {
|
|
186
|
+
const progress = milestoneTracker.getProgress();
|
|
187
|
+
console.log(chalk.cyan('Milestones:'));
|
|
188
|
+
console.log(chalk.gray(` Total: ${progress.total} | Completed: ${progress.completed} | Skipped: ${progress.skipped}`));
|
|
189
|
+
console.log(chalk.gray(` Progress: ${progress.percentage}%\n`));
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Show feature manifest progress if available
|
|
193
|
+
const manifestManager = new FeatureManifestManager(manager.getRunDir(run.id));
|
|
194
|
+
if (await manifestManager.load()) {
|
|
195
|
+
const progress = manifestManager.getProgress();
|
|
196
|
+
console.log(chalk.cyan('Features:'));
|
|
197
|
+
console.log(chalk.gray(` Total: ${progress.total} | Passed: ${progress.passed} | Remaining: ${progress.remaining}`));
|
|
198
|
+
console.log(chalk.gray(` Progress: ${progress.percentage}%\n`));
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
async function harnessHandoff(options) {
|
|
203
|
+
const cwd = process.cwd();
|
|
204
|
+
|
|
205
|
+
console.log(chalk.cyan.bold('\n🔗 Generate Handoff Package\n'));
|
|
206
|
+
|
|
207
|
+
const manager = new RunManager(cwd);
|
|
208
|
+
const run = await manager.getCurrentRun();
|
|
209
|
+
|
|
210
|
+
if (!run) {
|
|
211
|
+
console.log(chalk.yellow('No active harness run.\n'));
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const cwManager = new ContextWindowManager(manager.getRunDir(run.id), run.id);
|
|
216
|
+
|
|
217
|
+
if (options.consume) {
|
|
218
|
+
// Consume latest handoff
|
|
219
|
+
const handoff = await cwManager.consumeHandoffPackage('latest');
|
|
220
|
+
if (!handoff) {
|
|
221
|
+
console.log(chalk.yellow('No handoff package found.\n'));
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
console.log(chalk.green('✓ Handoff package consumed:'));
|
|
226
|
+
console.log(chalk.gray(JSON.stringify(handoff.toJSON(), null, 2)));
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Generate handoff from current state
|
|
231
|
+
// Load progress from linked session
|
|
232
|
+
const progressSummary = `Run ${run.id} - ${run.workflow} workflow`;
|
|
233
|
+
|
|
234
|
+
const handoff = await cwManager.generateHandoffPackage({
|
|
235
|
+
progressSummary,
|
|
236
|
+
completedWork: [],
|
|
237
|
+
nextSteps: [],
|
|
238
|
+
currentMilestone: { index: run.currentMilestoneIndex, title: '', percentage: 0 }
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
// Pause the run
|
|
242
|
+
await manager.pauseRun(run.id);
|
|
243
|
+
await cwManager.closeWindow('completed');
|
|
244
|
+
|
|
245
|
+
console.log(chalk.green(`✓ Handoff package generated`));
|
|
246
|
+
console.log(chalk.gray(` Source window: ${handoff.sourceWindowId}`));
|
|
247
|
+
console.log(chalk.gray(` Run paused. Resume with: adf harness resume\n`));
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
async function harnessEvents(options) {
|
|
251
|
+
const cwd = process.cwd();
|
|
252
|
+
|
|
253
|
+
console.log(chalk.cyan.bold('\n🔗 Harness Events\n'));
|
|
254
|
+
|
|
255
|
+
const manager = new RunManager(cwd);
|
|
256
|
+
const run = await manager.getCurrentRun();
|
|
257
|
+
|
|
258
|
+
if (!run) {
|
|
259
|
+
console.log(chalk.yellow('No active harness run.\n'));
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const logger = manager.getEventLogger(run.id);
|
|
264
|
+
|
|
265
|
+
if (options.stats) {
|
|
266
|
+
const stats = await logger.getStats();
|
|
267
|
+
console.log(chalk.white(`Total events: ${stats.total}\n`));
|
|
268
|
+
for (const [type, count] of Object.entries(stats.byType)) {
|
|
269
|
+
console.log(chalk.gray(` ${type}: ${count}`));
|
|
270
|
+
}
|
|
271
|
+
console.log('');
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const count = parseInt(options.tail) || 20;
|
|
276
|
+
const events = await logger.tail(count);
|
|
277
|
+
|
|
278
|
+
if (events.length === 0) {
|
|
279
|
+
console.log(chalk.gray('No events recorded.\n'));
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
console.log(chalk.white(`Last ${events.length} events:\n`));
|
|
284
|
+
for (const event of events) {
|
|
285
|
+
const time = new Date(event.timestamp).toLocaleTimeString();
|
|
286
|
+
const typeColor = {
|
|
287
|
+
answer: chalk.green,
|
|
288
|
+
skip: chalk.yellow,
|
|
289
|
+
error: chalk.red,
|
|
290
|
+
handoff: chalk.cyan,
|
|
291
|
+
checkpoint: chalk.gray,
|
|
292
|
+
window_open: chalk.blue,
|
|
293
|
+
window_close: chalk.blue
|
|
294
|
+
}[event.type] || chalk.white;
|
|
295
|
+
|
|
296
|
+
console.log(` ${chalk.gray(time)} ${typeColor(event.type.padEnd(18))} ${chalk.gray(JSON.stringify(event.data).slice(0, 80))}`);
|
|
297
|
+
}
|
|
298
|
+
console.log('');
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
async function harnessManifest(options) {
|
|
302
|
+
const cwd = process.cwd();
|
|
303
|
+
|
|
304
|
+
console.log(chalk.cyan.bold('\n🔗 Feature Manifest\n'));
|
|
305
|
+
|
|
306
|
+
const manager = new RunManager(cwd);
|
|
307
|
+
const run = await manager.getCurrentRun();
|
|
308
|
+
|
|
309
|
+
if (!run) {
|
|
310
|
+
console.log(chalk.yellow('No active harness run.\n'));
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const manifestManager = new FeatureManifestManager(manager.getRunDir(run.id));
|
|
315
|
+
|
|
316
|
+
if (!await manifestManager.load()) {
|
|
317
|
+
console.log(chalk.gray('No feature manifest generated yet.\n'));
|
|
318
|
+
console.log(chalk.gray('Complete an interview session to generate features.\n'));
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const manifest = manifestManager.getManifest();
|
|
323
|
+
const progress = manifestManager.getProgress();
|
|
324
|
+
|
|
325
|
+
console.log(chalk.white(`Features: ${progress.total} total | ${progress.passed} passed | ${progress.remaining} remaining`));
|
|
326
|
+
console.log(chalk.white(`Progress: ${progress.percentage}%\n`));
|
|
327
|
+
|
|
328
|
+
for (const feature of manifest.features) {
|
|
329
|
+
const icon = feature.passes ? chalk.green('✓') : chalk.gray('○');
|
|
330
|
+
console.log(` ${icon} ${feature.title}`);
|
|
331
|
+
if (feature.description) {
|
|
332
|
+
console.log(chalk.gray(` ${feature.description.slice(0, 80)}`));
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
console.log('');
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
module.exports = {
|
|
339
|
+
harnessStart,
|
|
340
|
+
harnessResume,
|
|
341
|
+
harnessStatus,
|
|
342
|
+
harnessHandoff,
|
|
343
|
+
harnessEvents,
|
|
344
|
+
harnessManifest
|
|
345
|
+
};
|
package/lib/commands/init.js
CHANGED
|
@@ -9,6 +9,9 @@ const {
|
|
|
9
9
|
} = require('../utils/project-detector');
|
|
10
10
|
const FrameworkDetector = require('../utils/framework-detector');
|
|
11
11
|
const ContextExtractor = require('../utils/context-extractor');
|
|
12
|
+
const ToolDetector = require('../utils/tool-detector');
|
|
13
|
+
const ToolRecommender = require('../utils/tool-recommender');
|
|
14
|
+
const ToolFeatureRegistry = require('../utils/tool-feature-registry');
|
|
12
15
|
const SynthesisEngine = require('../analysis/synthesis-engine');
|
|
13
16
|
const HeuristicGapAnalyzer = require('../analysis/heuristic-gap-analyzer');
|
|
14
17
|
const AIGapAnalyzer = require('../analysis/ai-gap-analyzer');
|
|
@@ -38,7 +41,8 @@ async function init(options) {
|
|
|
38
41
|
const frameworkNames = {
|
|
39
42
|
'agent-native': 'Agent-Native',
|
|
40
43
|
'openspec': 'OpenSpec',
|
|
41
|
-
'specification-driven': 'Specification-Driven'
|
|
44
|
+
'specification-driven': 'Specification-Driven',
|
|
45
|
+
'gemini-conductor': 'Gemini CLI Conductor'
|
|
42
46
|
};
|
|
43
47
|
|
|
44
48
|
const displayNames = detectedFrameworks
|
|
@@ -371,6 +375,7 @@ async function init(options) {
|
|
|
371
375
|
|
|
372
376
|
// Determine workflow/framework
|
|
373
377
|
let workflow;
|
|
378
|
+
let workflowContext = {};
|
|
374
379
|
|
|
375
380
|
if (options.rapid) {
|
|
376
381
|
workflow = 'rapid';
|
|
@@ -382,8 +387,14 @@ async function init(options) {
|
|
|
382
387
|
workflow = 'comprehensive';
|
|
383
388
|
console.log(chalk.blue('\nUsing: Level 3: Comprehensive (Agent-Native) - from --comprehensive flag'));
|
|
384
389
|
} else {
|
|
385
|
-
// Interactive workflow selection
|
|
386
|
-
|
|
390
|
+
// Interactive workflow selection (returns { workflow, context } or plain string for backward compat)
|
|
391
|
+
const workflowResult = await getWorkflowRecommendation(projectType);
|
|
392
|
+
if (typeof workflowResult === 'string') {
|
|
393
|
+
workflow = workflowResult;
|
|
394
|
+
} else {
|
|
395
|
+
workflow = workflowResult.workflow;
|
|
396
|
+
workflowContext = workflowResult.context || {};
|
|
397
|
+
}
|
|
387
398
|
}
|
|
388
399
|
|
|
389
400
|
// Create .adf directory
|
|
@@ -392,6 +403,74 @@ async function init(options) {
|
|
|
392
403
|
// AI already configured above - pass to interviewer
|
|
393
404
|
// Start interview
|
|
394
405
|
const interviewer = new Interviewer(workflow, cwd, null, aiConfig);
|
|
406
|
+
|
|
407
|
+
// Harness integration (opt-in via --harness flag)
|
|
408
|
+
if (options.harness) {
|
|
409
|
+
const RunManager = require('../harness/run-manager');
|
|
410
|
+
const ContextWindowManager = require('../harness/context-window-manager');
|
|
411
|
+
const MilestoneTracker = require('../harness/milestone-tracker');
|
|
412
|
+
const EventLogger = require('../harness/event-logger');
|
|
413
|
+
|
|
414
|
+
const runManager = new RunManager(cwd);
|
|
415
|
+
let run;
|
|
416
|
+
|
|
417
|
+
if (options.runId) {
|
|
418
|
+
// Resume existing harness run
|
|
419
|
+
run = await runManager.loadRun(options.runId);
|
|
420
|
+
if (!run) {
|
|
421
|
+
console.log(chalk.red(`Harness run not found: ${options.runId}\n`));
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
} else {
|
|
425
|
+
// Create new harness run
|
|
426
|
+
run = await runManager.createRun({
|
|
427
|
+
workflow,
|
|
428
|
+
mode: options.headless ? 'headless' : 'interactive',
|
|
429
|
+
provider: aiConfig ? {
|
|
430
|
+
id: aiConfig.provider,
|
|
431
|
+
model: aiConfig.model,
|
|
432
|
+
contextWindowSize: null
|
|
433
|
+
} : { id: null, model: null, contextWindowSize: null }
|
|
434
|
+
});
|
|
435
|
+
await runManager.startRun(run.id);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
const runDir = runManager.getRunDir(run.id);
|
|
439
|
+
const cwManager = new ContextWindowManager(runDir, run.id);
|
|
440
|
+
const eventLogger = EventLogger.forRun(runDir);
|
|
441
|
+
const milestoneTracker = new MilestoneTracker(runDir);
|
|
442
|
+
|
|
443
|
+
// Open context window if not already active
|
|
444
|
+
let currentWindow = cwManager.getCurrentWindow();
|
|
445
|
+
if (!currentWindow) {
|
|
446
|
+
currentWindow = await cwManager.openWindow();
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// Consume handoff if resuming
|
|
450
|
+
if (options.runId && run.status === 'running') {
|
|
451
|
+
const handoff = await cwManager.consumeHandoffPackage('latest');
|
|
452
|
+
if (handoff) {
|
|
453
|
+
interviewer.consumeHandoff(handoff);
|
|
454
|
+
console.log(chalk.green(`✓ Handoff consumed from window ${handoff.sourceWindowId}\n`));
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// Link session to run
|
|
459
|
+
run.sessionId = interviewer.sessionId;
|
|
460
|
+
await runManager.saveRun(run);
|
|
461
|
+
|
|
462
|
+
// Set harness on interviewer
|
|
463
|
+
interviewer.setHarness({
|
|
464
|
+
run,
|
|
465
|
+
runManager,
|
|
466
|
+
contextWindowManager: cwManager,
|
|
467
|
+
eventLogger,
|
|
468
|
+
milestoneTracker
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
console.log(chalk.cyan(`🔗 Harness: ${run.id} | Window: ${currentWindow.id}\n`));
|
|
472
|
+
}
|
|
473
|
+
|
|
395
474
|
const sessionPath = await interviewer.start();
|
|
396
475
|
|
|
397
476
|
// Show completion message
|
|
@@ -423,18 +502,64 @@ async function init(options) {
|
|
|
423
502
|
]);
|
|
424
503
|
|
|
425
504
|
if (deployNow) {
|
|
505
|
+
// Detect installed tools and generate recommendations
|
|
506
|
+
const detectSpinner = ora('Detecting installed tools...').start();
|
|
507
|
+
let installedTools;
|
|
508
|
+
try {
|
|
509
|
+
installedTools = await ToolDetector.detectAll(cwd);
|
|
510
|
+
const installedCount = [...installedTools.values()].filter(t => t.installed).length;
|
|
511
|
+
detectSpinner.succeed(`Detected ${installedCount} installed tool(s)`);
|
|
512
|
+
} catch {
|
|
513
|
+
installedTools = new Map();
|
|
514
|
+
detectSpinner.warn('Could not detect tools, showing all options');
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
const recommender = new ToolRecommender({
|
|
518
|
+
workflow,
|
|
519
|
+
projectType: projectType.type,
|
|
520
|
+
workflowContext,
|
|
521
|
+
installedTools
|
|
522
|
+
});
|
|
523
|
+
const recommendation = recommender.recommend();
|
|
524
|
+
|
|
525
|
+
// Display formatted recommendations
|
|
526
|
+
console.log(ToolRecommender.formatForDisplay(recommendation));
|
|
527
|
+
|
|
528
|
+
// Build pre-selected set from primary + complementary
|
|
529
|
+
const preSelected = new Set();
|
|
530
|
+
if (recommendation.primary) preSelected.add(recommendation.primary.toolId);
|
|
531
|
+
for (const c of recommendation.complementary) preSelected.add(c.toolId);
|
|
532
|
+
|
|
533
|
+
// Build choices for all deployable tools (exclude a2a protocol)
|
|
534
|
+
const allToolIds = Object.keys(ToolFeatureRegistry.REGISTRY).filter(id => {
|
|
535
|
+
const cfg = ToolFeatureRegistry.getDetectionConfig(id);
|
|
536
|
+
return cfg && cfg.category !== 'protocol';
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
const choices = allToolIds.map(id => {
|
|
540
|
+
const reg = ToolFeatureRegistry.REGISTRY[id];
|
|
541
|
+
const scored = recommendation.ranked.find(r => r.toolId === id);
|
|
542
|
+
const installStatus = installedTools.get(id);
|
|
543
|
+
const installed = installStatus && installStatus.installed;
|
|
544
|
+
|
|
545
|
+
let label = reg.name;
|
|
546
|
+
if (scored) label += chalk.gray(` (${scored.totalScore}pts)`);
|
|
547
|
+
if (installed) label += chalk.green(' *');
|
|
548
|
+
if (preSelected.has(id)) label += chalk.yellow(' (recommended)');
|
|
549
|
+
|
|
550
|
+
return {
|
|
551
|
+
name: label,
|
|
552
|
+
value: id,
|
|
553
|
+
checked: preSelected.has(id)
|
|
554
|
+
};
|
|
555
|
+
});
|
|
556
|
+
|
|
426
557
|
const { tools } = await inquirer.prompt([
|
|
427
558
|
{
|
|
428
559
|
type: 'checkbox',
|
|
429
560
|
name: 'tools',
|
|
430
561
|
message: 'Select tools (space to select, enter to confirm):',
|
|
431
|
-
choices
|
|
432
|
-
{ name: 'Windsurf', value: 'windsurf' },
|
|
433
|
-
{ name: 'Cursor', value: 'cursor' },
|
|
434
|
-
{ name: 'VSCode/Copilot', value: 'vscode' },
|
|
435
|
-
{ name: 'Claude Code', value: 'claude-code' },
|
|
436
|
-
{ name: 'Gemini CLI', value: 'gemini-cli' }
|
|
437
|
-
],
|
|
562
|
+
choices,
|
|
438
563
|
validate: (answer) => {
|
|
439
564
|
if (answer.length === 0) {
|
|
440
565
|
return 'You must choose at least one tool.';
|