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.
- package/CHANGELOG.md +5 -0
- package/README.md +4 -4
- package/package.json +1 -1
- package/scripts/agileflow-welcome.js +79 -0
- package/scripts/claude-tmux.sh +12 -36
- package/scripts/lib/ac-test-matcher.js +452 -0
- package/scripts/lib/audit-registry.js +58 -2
- package/scripts/lib/configure-features.js +35 -0
- package/scripts/lib/model-profiles.js +25 -5
- package/scripts/lib/quality-gates.js +163 -0
- package/scripts/lib/signal-detectors.js +43 -0
- package/scripts/lib/status-writer.js +255 -0
- package/scripts/lib/story-claiming.js +128 -45
- package/scripts/lib/task-sync.js +32 -38
- package/scripts/lib/tmux-audit-monitor.js +611 -0
- package/scripts/lib/tool-registry.yaml +241 -0
- package/scripts/lib/tool-shed.js +441 -0
- package/scripts/native-team-observer.js +219 -0
- package/scripts/obtain-context.js +14 -0
- package/scripts/ralph-loop.js +30 -5
- package/scripts/smart-detect.js +21 -0
- package/scripts/spawn-audit-sessions.js +372 -44
- package/scripts/team-manager.js +19 -0
- package/src/core/agents/a11y-analyzer-aria.md +155 -0
- package/src/core/agents/a11y-analyzer-forms.md +162 -0
- package/src/core/agents/a11y-analyzer-keyboard.md +175 -0
- package/src/core/agents/a11y-analyzer-semantic.md +153 -0
- package/src/core/agents/a11y-analyzer-visual.md +158 -0
- package/src/core/agents/a11y-consensus.md +248 -0
- package/src/core/agents/ads-consensus.md +74 -0
- package/src/core/agents/ads-generate.md +145 -0
- package/src/core/agents/ads-performance-tracker.md +197 -0
- package/src/core/agents/api-quality-analyzer-conventions.md +148 -0
- package/src/core/agents/api-quality-analyzer-docs.md +176 -0
- package/src/core/agents/api-quality-analyzer-errors.md +183 -0
- package/src/core/agents/api-quality-analyzer-pagination.md +171 -0
- package/src/core/agents/api-quality-analyzer-versioning.md +143 -0
- package/src/core/agents/api-quality-consensus.md +214 -0
- package/src/core/agents/arch-analyzer-circular.md +148 -0
- package/src/core/agents/arch-analyzer-complexity.md +171 -0
- package/src/core/agents/arch-analyzer-coupling.md +146 -0
- package/src/core/agents/arch-analyzer-layering.md +151 -0
- package/src/core/agents/arch-analyzer-patterns.md +162 -0
- package/src/core/agents/arch-consensus.md +227 -0
- package/src/core/commands/adr.md +1 -0
- package/src/core/commands/ads/generate.md +238 -0
- package/src/core/commands/ads/health.md +327 -0
- package/src/core/commands/ads/test-plan.md +317 -0
- package/src/core/commands/ads/track.md +288 -0
- package/src/core/commands/ads.md +28 -16
- package/src/core/commands/assign.md +1 -0
- package/src/core/commands/audit.md +43 -6
- package/src/core/commands/babysit.md +90 -6
- package/src/core/commands/baseline.md +1 -0
- package/src/core/commands/blockers.md +1 -0
- package/src/core/commands/board.md +1 -0
- package/src/core/commands/changelog.md +1 -0
- package/src/core/commands/choose.md +1 -0
- package/src/core/commands/ci.md +1 -0
- package/src/core/commands/code/accessibility.md +347 -0
- package/src/core/commands/code/api.md +297 -0
- package/src/core/commands/code/architecture.md +297 -0
- package/src/core/commands/code/completeness.md +43 -6
- package/src/core/commands/code/legal.md +43 -6
- package/src/core/commands/code/logic.md +43 -6
- package/src/core/commands/code/performance.md +43 -6
- package/src/core/commands/code/security.md +43 -6
- package/src/core/commands/code/test.md +43 -6
- package/src/core/commands/configure.md +1 -0
- package/src/core/commands/council.md +1 -0
- package/src/core/commands/deploy.md +1 -0
- package/src/core/commands/diagnose.md +1 -0
- package/src/core/commands/docs.md +1 -0
- package/src/core/commands/epic/edit.md +213 -0
- package/src/core/commands/epic.md +1 -0
- package/src/core/commands/export.md +238 -0
- package/src/core/commands/help.md +16 -1
- package/src/core/commands/ideate/discover.md +7 -3
- package/src/core/commands/ideate/features.md +65 -4
- package/src/core/commands/ideate/new.md +158 -124
- package/src/core/commands/impact.md +1 -0
- package/src/core/commands/learn/explain.md +118 -0
- package/src/core/commands/learn/glossary.md +135 -0
- package/src/core/commands/learn/patterns.md +138 -0
- package/src/core/commands/learn/tour.md +126 -0
- package/src/core/commands/migrate/codemods.md +151 -0
- package/src/core/commands/migrate/plan.md +131 -0
- package/src/core/commands/migrate/scan.md +114 -0
- package/src/core/commands/migrate/validate.md +119 -0
- package/src/core/commands/multi-expert.md +1 -0
- package/src/core/commands/pr.md +1 -0
- package/src/core/commands/review.md +1 -0
- package/src/core/commands/sprint.md +1 -0
- package/src/core/commands/status/undo.md +191 -0
- package/src/core/commands/status.md +1 -0
- package/src/core/commands/story/edit.md +204 -0
- package/src/core/commands/story/view.md +29 -7
- package/src/core/commands/story-validate.md +1 -0
- package/src/core/commands/story.md +1 -0
- package/src/core/commands/tdd.md +1 -0
- package/src/core/commands/team/start.md +10 -6
- package/src/core/commands/tests.md +1 -0
- package/src/core/commands/verify.md +27 -1
- package/src/core/commands/workflow.md +2 -0
- package/src/core/teams/backend.json +41 -0
- package/src/core/teams/frontend.json +41 -0
- package/src/core/teams/qa.json +41 -0
- package/src/core/teams/solo.json +35 -0
- package/src/core/templates/agileflow-metadata.json +5 -0
- package/tools/cli/commands/setup.js +85 -3
- package/tools/cli/commands/update.js +42 -0
- 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
|
|
package/scripts/ralph-loop.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
/**
|
package/scripts/smart-detect.js
CHANGED
|
@@ -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 {
|