agileflow 2.99.8 → 3.0.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 (65) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/lib/cache-provider.js +155 -0
  3. package/lib/codebase-indexer.js +1 -1
  4. package/lib/content-sanitizer.js +1 -0
  5. package/lib/dashboard-protocol.js +25 -0
  6. package/lib/dashboard-server.js +184 -133
  7. package/lib/errors.js +18 -0
  8. package/lib/file-cache.js +1 -1
  9. package/lib/flag-detection.js +11 -20
  10. package/lib/git-operations.js +15 -33
  11. package/lib/merge-operations.js +40 -34
  12. package/lib/process-executor.js +199 -0
  13. package/lib/registry-cache.js +13 -47
  14. package/lib/skill-loader.js +206 -0
  15. package/lib/smart-json-file.js +2 -4
  16. package/package.json +1 -1
  17. package/scripts/agileflow-configure.js +13 -12
  18. package/scripts/agileflow-statusline.sh +30 -0
  19. package/scripts/agileflow-welcome.js +181 -212
  20. package/scripts/auto-self-improve.js +3 -3
  21. package/scripts/claude-smart.sh +67 -0
  22. package/scripts/claude-tmux.sh +248 -161
  23. package/scripts/damage-control-multi-agent.js +227 -0
  24. package/scripts/lib/bus-utils.js +471 -0
  25. package/scripts/lib/configure-detect.js +5 -6
  26. package/scripts/lib/configure-features.js +44 -0
  27. package/scripts/lib/configure-repair.js +5 -6
  28. package/scripts/lib/configure-utils.js +2 -3
  29. package/scripts/lib/context-formatter.js +87 -8
  30. package/scripts/lib/damage-control-utils.js +37 -3
  31. package/scripts/lib/file-lock.js +392 -0
  32. package/scripts/lib/ideation-index.js +2 -5
  33. package/scripts/lib/lifecycle-detector.js +123 -0
  34. package/scripts/lib/process-cleanup.js +55 -81
  35. package/scripts/lib/scale-detector.js +357 -0
  36. package/scripts/lib/signal-detectors.js +779 -0
  37. package/scripts/lib/story-state-machine.js +1 -1
  38. package/scripts/lib/sync-ideation-status.js +2 -3
  39. package/scripts/lib/task-registry.js +7 -1
  40. package/scripts/lib/team-events.js +357 -0
  41. package/scripts/messaging-bridge.js +79 -36
  42. package/scripts/migrate-ideation-index.js +37 -14
  43. package/scripts/obtain-context.js +37 -19
  44. package/scripts/ralph-loop.js +3 -4
  45. package/scripts/smart-detect.js +390 -0
  46. package/scripts/team-manager.js +174 -30
  47. package/src/core/commands/audit.md +13 -11
  48. package/src/core/commands/babysit.md +162 -115
  49. package/src/core/commands/changelog.md +21 -4
  50. package/src/core/commands/configure.md +105 -2
  51. package/src/core/commands/debt.md +12 -2
  52. package/src/core/commands/feedback.md +7 -6
  53. package/src/core/commands/ideate/history.md +1 -1
  54. package/src/core/commands/ideate/new.md +5 -5
  55. package/src/core/commands/logic/audit.md +2 -2
  56. package/src/core/commands/pr.md +7 -6
  57. package/src/core/commands/research/analyze.md +28 -20
  58. package/src/core/commands/research/ask.md +43 -0
  59. package/src/core/commands/research/import.md +29 -21
  60. package/src/core/commands/research/list.md +8 -7
  61. package/src/core/commands/research/synthesize.md +356 -20
  62. package/src/core/commands/research/view.md +8 -5
  63. package/src/core/commands/review.md +24 -6
  64. package/src/core/commands/skill/create.md +34 -0
  65. package/tools/cli/lib/docs-setup.js +4 -0
@@ -21,7 +21,7 @@
21
21
 
22
22
  const fs = require('fs');
23
23
  const path = require('path');
24
- const { execFileSync } = require('child_process');
24
+ const { executeCommandSync } = require('../lib/process-executor');
25
25
 
26
26
  // Import loader and formatter modules
27
27
  const {
@@ -35,6 +35,14 @@ const {
35
35
 
36
36
  const { generateSummary, generateFullContent } = require('./lib/context-formatter');
37
37
 
38
+ // Smart detection for contextual feature routing
39
+ let smartDetect;
40
+ try {
41
+ smartDetect = require('./smart-detect');
42
+ } catch {
43
+ // smart-detect not available
44
+ }
45
+
38
46
  // Import validation
39
47
  let isValidCommandName;
40
48
  try {
@@ -122,28 +130,27 @@ function executeQueryMode(query) {
122
130
  return null;
123
131
  }
124
132
 
125
- try {
126
- const result = execFileSync('node', [queryScript, `--query=${query}`, '--budget=15000'], {
127
- encoding: 'utf8',
128
- maxBuffer: 50 * 1024 * 1024,
129
- });
130
-
131
- if (result.includes('No files found') || result.trim() === '') {
132
- return null;
133
- }
133
+ const result = executeCommandSync('node', [queryScript, `--query=${query}`, '--budget=15000'], {
134
+ timeout: 30000,
135
+ });
134
136
 
135
- return {
136
- mode: 'query',
137
- query: query,
138
- results: result.trim(),
139
- };
140
- } catch (err) {
141
- if (err.status === 2) {
137
+ if (!result.ok) {
138
+ if (result.exitCode === 2) {
142
139
  return null; // No results, fall back
143
140
  }
144
- console.error(`Query error: ${err.message}`);
141
+ console.error(`Query error: ${result.error}`);
145
142
  return null;
146
143
  }
144
+
145
+ if (result.data.includes('No files found') || result.data === '') {
146
+ return null;
147
+ }
148
+
149
+ return {
150
+ mode: 'query',
151
+ query: query,
152
+ results: result.data,
153
+ };
147
154
  }
148
155
 
149
156
  // =============================================================================
@@ -188,8 +195,19 @@ async function main() {
188
195
  process.stderr.write(`Context loaded in ${(prefetchElapsed / 1000).toFixed(1)}s\n`);
189
196
  }
190
197
 
198
+ // Run smart detection (contextual feature routing)
199
+ let smartDetectResults = null;
200
+ if (smartDetect) {
201
+ try {
202
+ smartDetectResults = smartDetect.analyze(prefetched);
203
+ smartDetect.writeRecommendations(smartDetectResults);
204
+ } catch {
205
+ // Smart detection is optional - don't block context output
206
+ }
207
+ }
208
+
191
209
  // Generate formatted output
192
- const formatOptions = { commandName, activeSections };
210
+ const formatOptions = { commandName, activeSections, smartDetectResults };
193
211
  const summary = generateSummary(prefetched, formatOptions);
194
212
  const fullContent = generateFullContent(prefetched, formatOptions);
195
213
 
@@ -35,7 +35,7 @@ const { execFileSync, spawnSync } = require('child_process');
35
35
  // Shared utilities
36
36
  const { c } = require('../lib/colors');
37
37
  const { getProjectRoot, getStatusPath, getSessionStatePath } = require('../lib/paths');
38
- const { safeReadJSON, safeWriteJSON } = require('../lib/errors');
38
+ const { safeReadJSON, safeWriteJSON, tryOptional } = require('../lib/errors');
39
39
  const { isValidEpicId, parseIntBounded } = require('../lib/validate');
40
40
 
41
41
  // Agent Teams integration (lazy-loaded)
@@ -428,7 +428,7 @@ function runCoverage(rootDir) {
428
428
 
429
429
  // Get screenshots directory from metadata or default
430
430
  function getScreenshotsDir(rootDir) {
431
- try {
431
+ return tryOptional(() => {
432
432
  const metadataPath = path.join(rootDir, 'docs/00-meta/agileflow-metadata.json');
433
433
  if (fs.existsSync(metadataPath)) {
434
434
  const metadata = JSON.parse(fs.readFileSync(metadataPath, 'utf8'));
@@ -436,8 +436,7 @@ function getScreenshotsDir(rootDir) {
436
436
  return metadata.ralph_loop.screenshots_dir;
437
437
  }
438
438
  }
439
- } catch (e) {}
440
- return './screenshots';
439
+ }, 'metadata read') || './screenshots';
441
440
  }
442
441
 
443
442
  // Run screenshot verification (Visual Mode)
@@ -0,0 +1,390 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * smart-detect.js
4
+ *
5
+ * Orchestrator for contextual feature routing.
6
+ * Gathers signals from prefetched context data, runs feature detectors,
7
+ * filters by lifecycle phase, and outputs recommendations.
8
+ *
9
+ * Called by obtain-context.js after prefetchAllData() returns.
10
+ * Output: docs/09-agents/smart-detect.json
11
+ *
12
+ * Usage (standalone):
13
+ * node scripts/smart-detect.js
14
+ *
15
+ * Usage (as module):
16
+ * const smartDetect = require('./smart-detect');
17
+ * const result = smartDetect.analyze(prefetched, sessionState, metadata);
18
+ */
19
+
20
+ 'use strict';
21
+
22
+ const fs = require('fs');
23
+ const path = require('path');
24
+ const { detectLifecyclePhase, getRelevantPhases } = require('./lib/lifecycle-detector');
25
+ const { runDetectorsForPhases } = require('./lib/signal-detectors');
26
+
27
+ let safeReadJSON, safeWriteJSON, tryOptional;
28
+ try {
29
+ const errors = require('../lib/errors');
30
+ safeReadJSON = errors.safeReadJSON;
31
+ safeWriteJSON = errors.safeWriteJSON;
32
+ tryOptional = errors.tryOptional;
33
+ } catch {
34
+ // Fallback for when running outside package context
35
+ safeReadJSON = (filePath, opts = {}) => {
36
+ try {
37
+ if (!fs.existsSync(filePath)) {
38
+ return opts.defaultValue !== undefined
39
+ ? { ok: true, data: opts.defaultValue }
40
+ : { ok: false, error: 'not found' };
41
+ }
42
+ return { ok: true, data: JSON.parse(fs.readFileSync(filePath, 'utf8')) };
43
+ } catch (e) {
44
+ return { ok: false, error: e.message };
45
+ }
46
+ };
47
+ safeWriteJSON = (filePath, data) => {
48
+ try {
49
+ const dir = path.dirname(filePath);
50
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
51
+ fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n');
52
+ return { ok: true };
53
+ } catch (e) {
54
+ return { ok: false, error: e.message };
55
+ }
56
+ };
57
+ tryOptional = (fn, _label) => { try { return fn(); } catch { return undefined; } };
58
+ }
59
+
60
+ // =============================================================================
61
+ // Signal Extraction from Prefetched Data
62
+ // =============================================================================
63
+
64
+ /**
65
+ * Extract structured signals from prefetched context data.
66
+ *
67
+ * @param {Object} prefetched - Data from obtain-context.js prefetchAllData()
68
+ * @param {Object} sessionState - Parsed session-state.json
69
+ * @param {Object} metadata - Parsed agileflow-metadata.json
70
+ * @returns {Object} Structured signals for detectors
71
+ */
72
+ function extractSignals(prefetched, sessionState, metadata) {
73
+ const statusJson = prefetched?.json?.statusJson || null;
74
+ const git = prefetched?.git || {};
75
+ const packageJsonPath = path.join(process.cwd(), 'package.json');
76
+
77
+ let packageJson = null;
78
+ try {
79
+ if (fs.existsSync(packageJsonPath)) {
80
+ packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
81
+ }
82
+ } catch {
83
+ // No package.json or invalid
84
+ }
85
+
86
+ // Determine current story
87
+ let story = null;
88
+ const currentStoryId = sessionState?.current_session?.current_story;
89
+ if (currentStoryId && statusJson?.stories?.[currentStoryId]) {
90
+ const s = statusJson.stories[currentStoryId];
91
+ story = { id: currentStoryId, ...s };
92
+ }
93
+
94
+ // Count stories by status
95
+ const counts = {};
96
+ let storyCount = 0;
97
+ if (statusJson?.stories) {
98
+ Object.values(statusJson.stories).forEach(s => {
99
+ const status = s.status || 'unknown';
100
+ counts[status] = (counts[status] || 0) + 1;
101
+ storyCount++;
102
+ });
103
+ }
104
+
105
+ // Git signals
106
+ const statusLines = (git.status || '').split('\n').filter(Boolean);
107
+ const changedFiles = statusLines.map(line => line.substring(3).trim());
108
+ const branch = git.branch || '';
109
+ const isClean = statusLines.length === 0;
110
+ const onFeatureBranch = branch !== 'main' && branch !== 'master' && branch !== '';
111
+
112
+ // Parse diff stats if available
113
+ let diffStats = null;
114
+ if (git.diffStat) {
115
+ const match = git.diffStat.match(/(\d+) insertions?.*?(\d+) deletions?/);
116
+ if (match) {
117
+ diffStats = { insertions: parseInt(match[1], 10), deletions: parseInt(match[2], 10) };
118
+ }
119
+ }
120
+
121
+ // File existence checks
122
+ const files = {
123
+ tsconfig: fs.existsSync('tsconfig.json'),
124
+ eslintrc: fs.existsSync('.eslintrc.js') || fs.existsSync('.eslintrc.json') || fs.existsSync('.eslintrc.yml'),
125
+ coverage: fs.existsSync('coverage/coverage-summary.json'),
126
+ playwright: fs.existsSync('playwright.config.ts') || fs.existsSync('playwright.config.js'),
127
+ screenshots: fs.existsSync('screenshots'),
128
+ ciConfig: fs.existsSync('.github/workflows') || fs.existsSync('.gitlab-ci.yml') || fs.existsSync('Jenkinsfile'),
129
+ expertiseDir: fs.existsSync('.agileflow/expertise'),
130
+ };
131
+
132
+ // Test state
133
+ let tests = { passing: null, hasTestSetup: false };
134
+ if (packageJson?.scripts?.test) {
135
+ tests.hasTestSetup = true;
136
+ }
137
+
138
+ // Plan mode detection
139
+ const planModeActive = (sessionState?.active_commands || []).some(
140
+ c => c.active_sections && c.active_sections.includes('plan-mode')
141
+ );
142
+
143
+ // Thresholds from metadata
144
+ const thresholds = metadata?.smart_detect?.thresholds || {};
145
+
146
+ return {
147
+ statusJson,
148
+ sessionState,
149
+ metadata,
150
+ git: {
151
+ branch,
152
+ filesChanged: statusLines.length,
153
+ changedFiles,
154
+ isClean,
155
+ onFeatureBranch,
156
+ diffStats,
157
+ commitCount: git.commitCount || 0,
158
+ },
159
+ packageJson,
160
+ story,
161
+ files,
162
+ tests,
163
+ counts,
164
+ storyCount,
165
+ thresholds,
166
+ session: {
167
+ planModeActive,
168
+ activeCommands: sessionState?.active_commands || [],
169
+ },
170
+ };
171
+ }
172
+
173
+ // =============================================================================
174
+ // Recommendation Filtering
175
+ // =============================================================================
176
+
177
+ /**
178
+ * Filter recommendations based on user preferences and session history.
179
+ *
180
+ * @param {Object[]} recommendations - Raw recommendations from detectors
181
+ * @param {Object} metadata - AgileFlow metadata (for disabled features)
182
+ * @param {Object} sessionState - Session state (for already-offered features)
183
+ * @returns {Object} Filtered and categorized recommendations
184
+ */
185
+ function filterRecommendations(recommendations, metadata, sessionState) {
186
+ const disabledFeatures = new Set(metadata?.smart_detect?.disabled_features || []);
187
+ const offeredFeatures = new Set(sessionState?.smart_detect?.features_offered || []);
188
+ const skippedFeatures = new Set(sessionState?.smart_detect?.features_skipped || []);
189
+
190
+ // Apply priority overrides from metadata
191
+ const priorityOverrides = metadata?.smart_detect?.priority_overrides || {};
192
+
193
+ const filtered = recommendations
194
+ .filter(r => !disabledFeatures.has(r.feature))
195
+ .filter(r => !skippedFeatures.has(r.feature))
196
+ .map(r => {
197
+ if (priorityOverrides[r.feature]) {
198
+ return { ...r, priority: priorityOverrides[r.feature] };
199
+ }
200
+ return r;
201
+ });
202
+
203
+ // Categorize: immediate (high priority, not yet offered), available (rest)
204
+ const immediate = filtered.filter(r => r.priority === 'high' && !offeredFeatures.has(r.feature));
205
+ const available = filtered.filter(r => r.priority !== 'high' || offeredFeatures.has(r.feature));
206
+
207
+ // Sort by priority within each category
208
+ const priorityOrder = { high: 0, medium: 1, low: 2 };
209
+ immediate.sort((a, b) => (priorityOrder[a.priority] || 2) - (priorityOrder[b.priority] || 2));
210
+ available.sort((a, b) => (priorityOrder[a.priority] || 2) - (priorityOrder[b.priority] || 2));
211
+
212
+ return { immediate, available };
213
+ }
214
+
215
+ // =============================================================================
216
+ // Main Analysis
217
+ // =============================================================================
218
+
219
+ /**
220
+ * Run full smart detection analysis.
221
+ *
222
+ * @param {Object} prefetched - Data from obtain-context.js prefetchAllData()
223
+ * @param {Object} [sessionState] - Parsed session-state.json (optional, extracted from prefetched if omitted)
224
+ * @param {Object} [metadata] - Parsed agileflow-metadata.json (optional, extracted from prefetched if omitted)
225
+ * @returns {Object} Smart detection results
226
+ */
227
+ function analyze(prefetched, sessionState, metadata) {
228
+ // Extract from prefetched if not provided directly
229
+ if (!sessionState) {
230
+ sessionState = prefetched?.json?.sessionState || {};
231
+ }
232
+ if (!metadata) {
233
+ metadata = prefetched?.json?.metadata || {};
234
+ }
235
+
236
+ // Check if smart-detect is disabled
237
+ if (metadata?.smart_detect?.enabled === false) {
238
+ return {
239
+ detected_at: new Date().toISOString(),
240
+ lifecycle_phase: 'unknown',
241
+ recommendations: { immediate: [], available: [], auto_enabled: {} },
242
+ signals_summary: {},
243
+ disabled: true,
244
+ };
245
+ }
246
+
247
+ // Extract signals
248
+ const signals = extractSignals(prefetched, sessionState, metadata);
249
+
250
+ // Detect lifecycle phase
251
+ const phaseResult = detectLifecyclePhase(signals);
252
+ const relevantPhases = getRelevantPhases(phaseResult.phase);
253
+
254
+ // Run detectors for relevant phases
255
+ const rawRecommendations = runDetectorsForPhases(relevantPhases, signals);
256
+
257
+ // Filter and categorize
258
+ const { immediate, available } = filterRecommendations(rawRecommendations, metadata, sessionState);
259
+
260
+ // Auto-enabled features (existing babysit modes)
261
+ const autoEnabled = detectAutoModes(signals);
262
+
263
+ // Build signals summary
264
+ const signalsSummary = {
265
+ story: signals.story
266
+ ? `${signals.story.id} (${signals.story.status || 'unknown'}${signals.story.owner ? ', ' + signals.story.owner : ''})`
267
+ : 'none',
268
+ files_changed: signals.git.filesChanged,
269
+ core_files: (signals.git.changedFiles || []).filter(f =>
270
+ /^(src\/(core|lib|shared)|lib\/|packages\/.*\/src\/)/.test(f)
271
+ ).length,
272
+ tests_passing: signals.tests.passing,
273
+ on_feature_branch: signals.git.onFeatureBranch,
274
+ story_counts: signals.counts,
275
+ };
276
+
277
+ return {
278
+ detected_at: new Date().toISOString(),
279
+ lifecycle_phase: phaseResult.phase,
280
+ phase_confidence: phaseResult.confidence,
281
+ phase_reason: phaseResult.reason,
282
+ recommendations: {
283
+ immediate,
284
+ available,
285
+ auto_enabled: autoEnabled,
286
+ },
287
+ signals_summary: signalsSummary,
288
+ };
289
+ }
290
+
291
+ /**
292
+ * Detect existing babysit auto-modes (loop, visual, coverage).
293
+ * Preserves backward compatibility with existing Smart Detection.
294
+ *
295
+ * @param {Object} signals
296
+ * @returns {Object} Auto-enabled mode flags
297
+ */
298
+ function detectAutoModes(signals) {
299
+ const { statusJson, story, files, packageJson } = signals;
300
+
301
+ // Loop mode: 3+ ready stories in same epic + test setup
302
+ let loopMode = false;
303
+ if (story?.epic && statusJson?.stories) {
304
+ const readyInEpic = Object.entries(statusJson.stories).filter(
305
+ ([, s]) => s.epic === story.epic && s.status === 'ready'
306
+ ).length;
307
+ loopMode = readyInEpic >= 3 && !!(packageJson?.scripts?.test);
308
+ }
309
+
310
+ // Visual mode: UI-related story or visual e2e setup
311
+ const visualMode = !!(files.playwright && files.screenshots);
312
+
313
+ // Coverage mode: coverage data exists
314
+ const coverageMode = !!files.coverage;
315
+
316
+ return {
317
+ loop_mode: loopMode,
318
+ visual_mode: visualMode,
319
+ coverage_mode: coverageMode,
320
+ };
321
+ }
322
+
323
+ // =============================================================================
324
+ // Output
325
+ // =============================================================================
326
+
327
+ /**
328
+ * Write recommendations to smart-detect.json.
329
+ *
330
+ * @param {Object} results - Analysis results from analyze()
331
+ * @param {string} [outputPath] - Override output path
332
+ * @returns {{ ok: boolean, error?: string }}
333
+ */
334
+ function writeRecommendations(results, outputPath) {
335
+ const targetPath = outputPath || path.join(process.cwd(), 'docs/09-agents/smart-detect.json');
336
+ return safeWriteJSON(targetPath, results, { createDir: true });
337
+ }
338
+
339
+ // =============================================================================
340
+ // CLI Entry Point
341
+ // =============================================================================
342
+
343
+ if (require.main === module) {
344
+ // Run standalone - gather our own data
345
+ const statusJsonResult = safeReadJSON(
346
+ path.join(process.cwd(), 'docs/09-agents/status.json'),
347
+ { defaultValue: {} }
348
+ );
349
+ const sessionStateResult = safeReadJSON(
350
+ path.join(process.cwd(), 'docs/09-agents/session-state.json'),
351
+ { defaultValue: {} }
352
+ );
353
+ const metadataResult = safeReadJSON(
354
+ path.join(process.cwd(), 'docs/00-meta/agileflow-metadata.json'),
355
+ { defaultValue: {} }
356
+ );
357
+
358
+ // Build minimal prefetched structure
359
+ const { execSync } = require('child_process');
360
+ const gitBranch = tryOptional(() => execSync('git branch --show-current', { encoding: 'utf8' }).trim(), 'git branch') || '';
361
+ const gitStatus = tryOptional(() => execSync('git status --short', { encoding: 'utf8' }).trim(), 'git status') || '';
362
+
363
+ const prefetched = {
364
+ json: {
365
+ statusJson: statusJsonResult.ok ? statusJsonResult.data : {},
366
+ sessionState: sessionStateResult.ok ? sessionStateResult.data : {},
367
+ metadata: metadataResult.ok ? metadataResult.data : {},
368
+ },
369
+ git: {
370
+ branch: gitBranch,
371
+ status: gitStatus,
372
+ },
373
+ };
374
+
375
+ const results = analyze(prefetched);
376
+ writeRecommendations(results);
377
+
378
+ // Output summary to stderr for visibility
379
+ const { immediate, available } = results.recommendations;
380
+ process.stderr.write(`Smart detect: phase=${results.lifecycle_phase}, `);
381
+ process.stderr.write(`${immediate.length} immediate, ${available.length} available\n`);
382
+ }
383
+
384
+ module.exports = {
385
+ analyze,
386
+ writeRecommendations,
387
+ extractSignals,
388
+ filterRecommendations,
389
+ detectAutoModes,
390
+ };