agileflow 3.4.0 → 3.4.1

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 (112) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/README.md +4 -4
  3. package/package.json +1 -1
  4. package/scripts/agileflow-welcome.js +79 -0
  5. package/scripts/claude-tmux.sh +12 -36
  6. package/scripts/lib/ac-test-matcher.js +452 -0
  7. package/scripts/lib/audit-registry.js +58 -2
  8. package/scripts/lib/configure-features.js +35 -0
  9. package/scripts/lib/model-profiles.js +25 -5
  10. package/scripts/lib/quality-gates.js +163 -0
  11. package/scripts/lib/signal-detectors.js +43 -0
  12. package/scripts/lib/status-writer.js +255 -0
  13. package/scripts/lib/story-claiming.js +128 -45
  14. package/scripts/lib/task-sync.js +32 -38
  15. package/scripts/lib/tmux-audit-monitor.js +611 -0
  16. package/scripts/lib/tool-registry.yaml +241 -0
  17. package/scripts/lib/tool-shed.js +441 -0
  18. package/scripts/native-team-observer.js +219 -0
  19. package/scripts/obtain-context.js +14 -0
  20. package/scripts/ralph-loop.js +30 -5
  21. package/scripts/smart-detect.js +21 -0
  22. package/scripts/spawn-audit-sessions.js +372 -44
  23. package/scripts/team-manager.js +19 -0
  24. package/src/core/agents/a11y-analyzer-aria.md +155 -0
  25. package/src/core/agents/a11y-analyzer-forms.md +162 -0
  26. package/src/core/agents/a11y-analyzer-keyboard.md +175 -0
  27. package/src/core/agents/a11y-analyzer-semantic.md +153 -0
  28. package/src/core/agents/a11y-analyzer-visual.md +158 -0
  29. package/src/core/agents/a11y-consensus.md +248 -0
  30. package/src/core/agents/ads-consensus.md +74 -0
  31. package/src/core/agents/ads-generate.md +145 -0
  32. package/src/core/agents/ads-performance-tracker.md +197 -0
  33. package/src/core/agents/api-quality-analyzer-conventions.md +148 -0
  34. package/src/core/agents/api-quality-analyzer-docs.md +176 -0
  35. package/src/core/agents/api-quality-analyzer-errors.md +183 -0
  36. package/src/core/agents/api-quality-analyzer-pagination.md +171 -0
  37. package/src/core/agents/api-quality-analyzer-versioning.md +143 -0
  38. package/src/core/agents/api-quality-consensus.md +214 -0
  39. package/src/core/agents/arch-analyzer-circular.md +148 -0
  40. package/src/core/agents/arch-analyzer-complexity.md +171 -0
  41. package/src/core/agents/arch-analyzer-coupling.md +146 -0
  42. package/src/core/agents/arch-analyzer-layering.md +151 -0
  43. package/src/core/agents/arch-analyzer-patterns.md +162 -0
  44. package/src/core/agents/arch-consensus.md +227 -0
  45. package/src/core/commands/adr.md +1 -0
  46. package/src/core/commands/ads/generate.md +238 -0
  47. package/src/core/commands/ads/health.md +327 -0
  48. package/src/core/commands/ads/test-plan.md +317 -0
  49. package/src/core/commands/ads/track.md +288 -0
  50. package/src/core/commands/ads.md +28 -16
  51. package/src/core/commands/assign.md +1 -0
  52. package/src/core/commands/audit.md +43 -6
  53. package/src/core/commands/babysit.md +90 -6
  54. package/src/core/commands/baseline.md +1 -0
  55. package/src/core/commands/blockers.md +1 -0
  56. package/src/core/commands/board.md +1 -0
  57. package/src/core/commands/changelog.md +1 -0
  58. package/src/core/commands/choose.md +1 -0
  59. package/src/core/commands/ci.md +1 -0
  60. package/src/core/commands/code/accessibility.md +347 -0
  61. package/src/core/commands/code/api.md +297 -0
  62. package/src/core/commands/code/architecture.md +297 -0
  63. package/src/core/commands/code/completeness.md +43 -6
  64. package/src/core/commands/code/legal.md +43 -6
  65. package/src/core/commands/code/logic.md +43 -6
  66. package/src/core/commands/code/performance.md +43 -6
  67. package/src/core/commands/code/security.md +43 -6
  68. package/src/core/commands/code/test.md +43 -6
  69. package/src/core/commands/configure.md +1 -0
  70. package/src/core/commands/council.md +1 -0
  71. package/src/core/commands/deploy.md +1 -0
  72. package/src/core/commands/diagnose.md +1 -0
  73. package/src/core/commands/docs.md +1 -0
  74. package/src/core/commands/epic/edit.md +213 -0
  75. package/src/core/commands/epic.md +1 -0
  76. package/src/core/commands/export.md +238 -0
  77. package/src/core/commands/help.md +16 -1
  78. package/src/core/commands/ideate/discover.md +7 -3
  79. package/src/core/commands/ideate/features.md +65 -4
  80. package/src/core/commands/ideate/new.md +158 -124
  81. package/src/core/commands/impact.md +1 -0
  82. package/src/core/commands/learn/explain.md +118 -0
  83. package/src/core/commands/learn/glossary.md +135 -0
  84. package/src/core/commands/learn/patterns.md +138 -0
  85. package/src/core/commands/learn/tour.md +126 -0
  86. package/src/core/commands/migrate/codemods.md +151 -0
  87. package/src/core/commands/migrate/plan.md +131 -0
  88. package/src/core/commands/migrate/scan.md +114 -0
  89. package/src/core/commands/migrate/validate.md +119 -0
  90. package/src/core/commands/multi-expert.md +1 -0
  91. package/src/core/commands/pr.md +1 -0
  92. package/src/core/commands/review.md +1 -0
  93. package/src/core/commands/sprint.md +1 -0
  94. package/src/core/commands/status/undo.md +191 -0
  95. package/src/core/commands/status.md +1 -0
  96. package/src/core/commands/story/edit.md +204 -0
  97. package/src/core/commands/story/view.md +29 -7
  98. package/src/core/commands/story-validate.md +1 -0
  99. package/src/core/commands/story.md +1 -0
  100. package/src/core/commands/tdd.md +1 -0
  101. package/src/core/commands/team/start.md +10 -6
  102. package/src/core/commands/tests.md +1 -0
  103. package/src/core/commands/verify.md +27 -1
  104. package/src/core/commands/workflow.md +2 -0
  105. package/src/core/teams/backend.json +41 -0
  106. package/src/core/teams/frontend.json +41 -0
  107. package/src/core/teams/qa.json +41 -0
  108. package/src/core/teams/solo.json +35 -0
  109. package/src/core/templates/agileflow-metadata.json +5 -0
  110. package/tools/cli/commands/setup.js +85 -3
  111. package/tools/cli/commands/update.js +42 -0
  112. package/tools/cli/installers/ide/claude-code.js +68 -0
@@ -0,0 +1,219 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * native-team-observer.js - PostToolUse hook for native Agent Teams observability
4
+ *
5
+ * Logs native TeamCreate, SendMessage, and ListTeams tool calls to the
6
+ * AgileFlow JSONL bus so that native mode has observability parity with
7
+ * subagent mode (where team-manager.js handles dual-write).
8
+ *
9
+ * Exit codes:
10
+ * 0 - Always (observability hook, never blocks)
11
+ *
12
+ * Usage: Configured as PostToolUse hook in .claude/settings.json
13
+ */
14
+
15
+ const fs = require('fs');
16
+ const path = require('path');
17
+
18
+ // Tools this hook observes
19
+ const NATIVE_TEAM_TOOLS = ['TeamCreate', 'SendMessage', 'ListTeams'];
20
+
21
+ // Lazy-load modules to minimize startup cost
22
+ let _featureFlags;
23
+ function getFeatureFlags() {
24
+ if (!_featureFlags) {
25
+ const candidates = [
26
+ path.join(__dirname, '..', 'lib', 'feature-flags.js'),
27
+ path.join(__dirname, 'lib', 'feature-flags.js'),
28
+ path.join(process.cwd(), '.agileflow', 'lib', 'feature-flags.js'),
29
+ ];
30
+ for (const candidate of candidates) {
31
+ try {
32
+ if (fs.existsSync(candidate)) {
33
+ _featureFlags = require(candidate);
34
+ return _featureFlags;
35
+ }
36
+ } catch (e) {
37
+ // Try next
38
+ }
39
+ }
40
+ return null;
41
+ }
42
+ return _featureFlags;
43
+ }
44
+
45
+ let _paths;
46
+ function getPaths() {
47
+ if (!_paths) {
48
+ const candidates = [
49
+ path.join(__dirname, '..', 'lib', 'paths.js'),
50
+ path.join(__dirname, 'lib', 'paths.js'),
51
+ path.join(process.cwd(), '.agileflow', 'lib', 'paths.js'),
52
+ ];
53
+ for (const candidate of candidates) {
54
+ try {
55
+ if (fs.existsSync(candidate)) {
56
+ _paths = require(candidate);
57
+ return _paths;
58
+ }
59
+ } catch (e) {
60
+ // Try next
61
+ }
62
+ }
63
+ return null;
64
+ }
65
+ return _paths;
66
+ }
67
+
68
+ let _messagingBridge;
69
+ function getMessagingBridge() {
70
+ if (!_messagingBridge) {
71
+ const candidates = [
72
+ path.join(__dirname, 'messaging-bridge.js'),
73
+ path.join(process.cwd(), '.agileflow', 'scripts', 'messaging-bridge.js'),
74
+ ];
75
+ for (const candidate of candidates) {
76
+ try {
77
+ if (fs.existsSync(candidate)) {
78
+ _messagingBridge = require(candidate);
79
+ return _messagingBridge;
80
+ }
81
+ } catch (e) {
82
+ // Try next
83
+ }
84
+ }
85
+ return null;
86
+ }
87
+ return _messagingBridge;
88
+ }
89
+
90
+ /**
91
+ * Read trace_id from session-state.json active_team.
92
+ * Returns undefined if not available.
93
+ */
94
+ function getTraceId(rootDir) {
95
+ try {
96
+ const paths = getPaths();
97
+ if (!paths) return undefined;
98
+ const sessionStatePath = paths.getSessionStatePath(rootDir);
99
+ if (!fs.existsSync(sessionStatePath)) return undefined;
100
+ const state = JSON.parse(fs.readFileSync(sessionStatePath, 'utf8'));
101
+ return state.active_team?.trace_id;
102
+ } catch (e) {
103
+ return undefined;
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Get the active_team template name from session-state.json.
109
+ */
110
+ function getActiveTeamTemplate(rootDir) {
111
+ try {
112
+ const paths = getPaths();
113
+ if (!paths) return undefined;
114
+ const sessionStatePath = paths.getSessionStatePath(rootDir);
115
+ if (!fs.existsSync(sessionStatePath)) return undefined;
116
+ const state = JSON.parse(fs.readFileSync(sessionStatePath, 'utf8'));
117
+ return state.active_team?.template;
118
+ } catch (e) {
119
+ return undefined;
120
+ }
121
+ }
122
+
123
+ /**
124
+ * Check if session-state has an active_team.
125
+ */
126
+ function hasActiveTeam(rootDir) {
127
+ try {
128
+ const paths = getPaths();
129
+ if (!paths) return false;
130
+ const sessionStatePath = paths.getSessionStatePath(rootDir);
131
+ if (!fs.existsSync(sessionStatePath)) return false;
132
+ const state = JSON.parse(fs.readFileSync(sessionStatePath, 'utf8'));
133
+ return !!state.active_team;
134
+ } catch (e) {
135
+ return false;
136
+ }
137
+ }
138
+
139
+ // Read stdin and process
140
+ let input = '';
141
+ process.stdin.on('data', chunk => (input += chunk));
142
+ process.stdin.on('end', () => {
143
+ try {
144
+ const context = JSON.parse(input);
145
+ const toolName = context.tool_name;
146
+
147
+ // Skip non-matching tools
148
+ if (!NATIVE_TEAM_TOOLS.includes(toolName)) {
149
+ process.exit(0);
150
+ }
151
+
152
+ // Check if native Agent Teams is enabled
153
+ const featureFlags = getFeatureFlags();
154
+ if (!featureFlags || !featureFlags.isAgentTeamsEnabled({ rootDir: process.cwd() })) {
155
+ process.exit(0);
156
+ }
157
+
158
+ const rootDir = process.cwd();
159
+ const bridge = getMessagingBridge();
160
+ if (!bridge) {
161
+ process.exit(0);
162
+ }
163
+
164
+ const traceId = getTraceId(rootDir);
165
+ const toolInput = context.tool_input || {};
166
+ const toolOutput = context.tool_output;
167
+
168
+ switch (toolName) {
169
+ case 'TeamCreate': {
170
+ const teamName = toolInput.name || 'unknown';
171
+ const teammateCount = Array.isArray(toolInput.teammates)
172
+ ? toolInput.teammates.length
173
+ : undefined;
174
+ bridge.logNativeTeamCreate(rootDir, teamName, traceId, teammateCount);
175
+ break;
176
+ }
177
+
178
+ case 'SendMessage': {
179
+ const to = toolInput.to || 'unknown';
180
+ const content = toolInput.message || toolInput.content || '';
181
+ bridge.logNativeSend(rootDir, 'lead', to, content, traceId);
182
+ break;
183
+ }
184
+
185
+ case 'ListTeams': {
186
+ // Detect team completion: ListTeams returns no active teams but
187
+ // session-state still has an active_team
188
+ if (hasActiveTeam(rootDir)) {
189
+ let noActiveTeams = false;
190
+ try {
191
+ if (typeof toolOutput === 'string') {
192
+ const parsed = JSON.parse(toolOutput);
193
+ noActiveTeams = Array.isArray(parsed) ? parsed.length === 0 : !parsed.teams?.length;
194
+ } else if (typeof toolOutput === 'object' && toolOutput !== null) {
195
+ noActiveTeams = Array.isArray(toolOutput)
196
+ ? toolOutput.length === 0
197
+ : !toolOutput.teams?.length;
198
+ }
199
+ } catch (e) {
200
+ // Can't parse output - skip completion detection
201
+ }
202
+
203
+ if (noActiveTeams) {
204
+ const template = getActiveTeamTemplate(rootDir) || 'unknown';
205
+ bridge.logNativeTeamCompleted(rootDir, template, traceId, 'completed');
206
+ }
207
+ }
208
+ break;
209
+ }
210
+ }
211
+ } catch (e) {
212
+ // Fail-open: observability hook should never block
213
+ }
214
+
215
+ process.exit(0);
216
+ });
217
+
218
+ // Handle empty stdin (timeout safety)
219
+ setTimeout(() => process.exit(0), 4000);
@@ -158,6 +158,20 @@ function executeQueryMode(query) {
158
158
  // =============================================================================
159
159
 
160
160
  async function main() {
161
+ // Guard: Check .agileflow directory exists (covers all 94+ commands)
162
+ if (!fs.existsSync('.agileflow') && !fs.existsSync('docs/09-agents/status.json')) {
163
+ const red = '\x1b[38;5;203m';
164
+ const bold = '\x1b[1m';
165
+ const reset = '\x1b[0m';
166
+ const dim = '\x1b[2m';
167
+ console.error(`${red}${bold}AgileFlow is not initialized in this directory.${reset}`);
168
+ console.error(`${dim}Run: ${reset}${bold}npx agileflow setup${reset}`);
169
+ console.error(
170
+ `${dim}This will create the .agileflow/ directory with commands, hooks, and configuration.${reset}`
171
+ );
172
+ process.exit(1);
173
+ }
174
+
161
175
  // Register command for PreCompact
162
176
  registerCommand();
163
177
 
@@ -279,16 +279,41 @@ const DISCRETION_CONDITIONS = {
279
279
  if (!Array.isArray(ac) || ac.length === 0) {
280
280
  return { passed: true, message: 'No AC defined (assuming complete)' };
281
281
  }
282
- // Check for ac_status field or assume AC are verified if tests pass
282
+ // Check for ac_status field (supports auto-verified and likely-covered from ac-test-matcher)
283
283
  const acStatus = story.ac_status || {};
284
- const allVerified = ac.every((_, i) => acStatus[i] === 'verified' || acStatus[i] === true);
284
+ const verifiedStatuses = ['verified', 'auto-verified', 'likely-covered'];
285
+ const verifiedCount = ac.filter(
286
+ (_, i) => verifiedStatuses.includes(acStatus[i]) || acStatus[i] === true
287
+ ).length;
288
+ const allVerified = verifiedCount === ac.length;
285
289
  return {
286
290
  passed: allVerified,
287
- message: allVerified
288
- ? 'All AC verified'
289
- : `${Object.values(acStatus).filter(v => v === 'verified' || v === true).length}/${ac.length} AC verified`,
291
+ message: allVerified ? 'All AC verified' : `${verifiedCount}/${ac.length} AC verified`,
290
292
  };
291
293
  },
294
+
295
+ // AC test coverage condition (uses ac-test-matcher for automated checks)
296
+ 'ac test coverage sufficient': (rootDir, ctx) => {
297
+ const storyId = ctx.currentStoryId;
298
+ if (!storyId) {
299
+ return { passed: false, message: 'No story ID in context' };
300
+ }
301
+ try {
302
+ const { matchACToTests } = require(path.join(__dirname, 'lib', 'ac-test-matcher'));
303
+ const result = matchACToTests(storyId, rootDir);
304
+ if (result.error) {
305
+ return { passed: false, message: result.error };
306
+ }
307
+ const threshold = ctx.coverageThreshold || 0.5;
308
+ const passed = result.coverage >= threshold;
309
+ return {
310
+ passed,
311
+ message: `AC test coverage: ${Math.round(result.coverage * 100)}% (${result.matched.length}/${result.total} matched, threshold: ${Math.round(threshold * 100)}%)`,
312
+ };
313
+ } catch (e) {
314
+ return { passed: false, message: `AC matcher error: ${e.message}` };
315
+ }
316
+ },
292
317
  };
293
318
 
294
319
  /**
@@ -24,6 +24,7 @@ const path = require('path');
24
24
  const { detectLifecyclePhase, getRelevantPhases } = require('./lib/lifecycle-detector');
25
25
  const { runDetectorsForPhases } = require('./lib/signal-detectors');
26
26
  const { buildCatalogWithStatus } = require('./lib/feature-catalog');
27
+ const { detectScale, getScaleRecommendations } = require('./lib/scale-detector');
27
28
 
28
29
  let safeReadJSON, safeWriteJSON, tryOptional;
29
30
  try {
@@ -158,6 +159,24 @@ function extractSignals(prefetched, sessionState, metadata) {
158
159
  // Thresholds from metadata
159
160
  const thresholds = metadata?.smart_detect?.thresholds || {};
160
161
 
162
+ // Scale detection (cached, <200ms)
163
+ let scale = null;
164
+ try {
165
+ const scaleResult = detectScale({
166
+ rootDir: process.cwd(),
167
+ statusJson,
168
+ sessionState,
169
+ });
170
+ scale = {
171
+ tier: scaleResult.scale,
172
+ metrics: scaleResult.metrics,
173
+ recommendations: getScaleRecommendations(scaleResult.scale),
174
+ fromCache: scaleResult.fromCache,
175
+ };
176
+ } catch {
177
+ // Scale detection failure is non-critical
178
+ }
179
+
161
180
  return {
162
181
  statusJson,
163
182
  sessionState,
@@ -178,6 +197,7 @@ function extractSignals(prefetched, sessionState, metadata) {
178
197
  counts,
179
198
  storyCount,
180
199
  thresholds,
200
+ scale,
181
201
  session: {
182
202
  planModeActive,
183
203
  activeCommands: sessionState?.active_commands || [],
@@ -300,6 +320,7 @@ function analyze(prefetched, sessionState, metadata) {
300
320
  tests_passing: signals.tests.passing,
301
321
  on_feature_branch: signals.git.onFeatureBranch,
302
322
  story_counts: signals.counts,
323
+ scale: signals.scale ? signals.scale.tier : null,
303
324
  };
304
325
 
305
326
  return {