agileflow 2.95.2 → 2.96.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/CHANGELOG.md +10 -0
- package/README.md +6 -6
- package/lib/api-routes.js +605 -0
- package/lib/api-server.js +260 -0
- package/lib/claude-cli-bridge.js +221 -0
- package/lib/dashboard-protocol.js +541 -0
- package/lib/dashboard-server.js +1601 -0
- package/lib/drivers/claude-driver.ts +310 -0
- package/lib/drivers/codex-driver.ts +454 -0
- package/lib/drivers/driver-manager.ts +158 -0
- package/lib/drivers/gemini-driver.ts +485 -0
- package/lib/drivers/index.ts +17 -0
- package/lib/flag-detection.js +350 -0
- package/lib/git-operations.js +267 -0
- package/lib/lock-file.js +144 -0
- package/lib/merge-operations.js +959 -0
- package/lib/protocol/driver.ts +360 -0
- package/lib/protocol/index.ts +12 -0
- package/lib/protocol/ir.ts +271 -0
- package/lib/session-display.js +330 -0
- package/lib/worktree-operations.js +221 -0
- package/package.json +2 -2
- package/scripts/agileflow-welcome.js +272 -24
- package/scripts/api-server-runner.js +177 -0
- package/scripts/archive-completed-stories.sh +22 -0
- package/scripts/automation-run-due.js +126 -0
- package/scripts/backfill-ideation-status.js +124 -0
- package/scripts/claude-tmux.sh +62 -1
- package/scripts/context-loader.js +292 -0
- package/scripts/dashboard-serve.js +323 -0
- package/scripts/lib/automation-registry.js +544 -0
- package/scripts/lib/automation-runner.js +476 -0
- package/scripts/lib/concurrency-limiter.js +513 -0
- package/scripts/lib/configure-features.js +46 -0
- package/scripts/lib/context-formatter.js +61 -0
- package/scripts/lib/damage-control-utils.js +29 -4
- package/scripts/lib/hook-metrics.js +324 -0
- package/scripts/lib/ideation-index.js +1196 -0
- package/scripts/lib/process-cleanup.js +359 -0
- package/scripts/lib/quality-gates.js +574 -0
- package/scripts/lib/status-task-bridge.js +522 -0
- package/scripts/lib/sync-ideation-status.js +292 -0
- package/scripts/lib/task-registry-cache.js +490 -0
- package/scripts/lib/task-registry.js +1181 -0
- package/scripts/migrate-ideation-index.js +515 -0
- package/scripts/precompact-context.sh +104 -0
- package/scripts/ralph-loop.js +2 -2
- package/scripts/session-manager.js +363 -2770
- package/scripts/spawn-parallel.js +45 -9
- package/src/core/agents/api-validator.md +180 -0
- package/src/core/agents/api.md +2 -0
- package/src/core/agents/code-reviewer.md +289 -0
- package/src/core/agents/configuration/damage-control.md +17 -0
- package/src/core/agents/database.md +2 -0
- package/src/core/agents/error-analyzer.md +203 -0
- package/src/core/agents/logic-analyzer-edge.md +171 -0
- package/src/core/agents/logic-analyzer-flow.md +254 -0
- package/src/core/agents/logic-analyzer-invariant.md +207 -0
- package/src/core/agents/logic-analyzer-race.md +267 -0
- package/src/core/agents/logic-analyzer-type.md +218 -0
- package/src/core/agents/logic-consensus.md +256 -0
- package/src/core/agents/orchestrator.md +89 -1
- package/src/core/agents/schema-validator.md +451 -0
- package/src/core/agents/team-coordinator.md +328 -0
- package/src/core/agents/ui-validator.md +328 -0
- package/src/core/agents/ui.md +2 -0
- package/src/core/commands/api.md +267 -0
- package/src/core/commands/automate.md +415 -0
- package/src/core/commands/babysit.md +290 -9
- package/src/core/commands/ideate/history.md +403 -0
- package/src/core/commands/{ideate.md → ideate/new.md} +244 -34
- package/src/core/commands/logic/audit.md +368 -0
- package/src/core/commands/roadmap/analyze.md +1 -1
- package/src/core/experts/documentation/expertise.yaml +29 -2
- package/src/core/templates/CONTEXT.md.example +49 -0
- package/src/core/templates/claude-settings.advanced.example.json +4 -0
- package/tools/cli/commands/serve.js +456 -0
- package/tools/cli/installers/core/installer.js +7 -2
- package/tools/cli/installers/ide/claude-code.js +85 -0
- package/tools/cli/lib/content-injector.js +27 -1
- package/tools/cli/lib/ui.js +26 -57
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hook-metrics.js - Hook timing and performance metrics utility
|
|
3
|
+
*
|
|
4
|
+
* Provides utilities for recording hook execution metrics to session-state.json.
|
|
5
|
+
* This enables observability into hook performance without changing tech stack.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* const { startHookTimer, recordHookMetrics, getHookMetrics } = require('./lib/hook-metrics');
|
|
9
|
+
*
|
|
10
|
+
* // At start of hook
|
|
11
|
+
* const timer = startHookTimer('SessionStart', 'welcome');
|
|
12
|
+
*
|
|
13
|
+
* // ... do hook work ...
|
|
14
|
+
*
|
|
15
|
+
* // At end of hook (success)
|
|
16
|
+
* recordHookMetrics(timer, 'success');
|
|
17
|
+
*
|
|
18
|
+
* // Or on failure
|
|
19
|
+
* recordHookMetrics(timer, 'error', 'Parse error');
|
|
20
|
+
*
|
|
21
|
+
* Metrics structure in session-state.json:
|
|
22
|
+
* {
|
|
23
|
+
* "hook_metrics": {
|
|
24
|
+
* "last_updated": "2026-02-02T12:00:00.000Z",
|
|
25
|
+
* "session_total_ms": 565,
|
|
26
|
+
* "hooks": {
|
|
27
|
+
* "SessionStart": {
|
|
28
|
+
* "welcome": { "duration_ms": 245, "status": "success", "at": "..." },
|
|
29
|
+
* "archive": { "duration_ms": 120, "status": "success", "at": "..." }
|
|
30
|
+
* },
|
|
31
|
+
* "PreToolUse": {
|
|
32
|
+
* "damage_control_bash": { "duration_ms": 50, "status": "success", "at": "..." }
|
|
33
|
+
* }
|
|
34
|
+
* }
|
|
35
|
+
* }
|
|
36
|
+
* }
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
const fs = require('fs');
|
|
40
|
+
const path = require('path');
|
|
41
|
+
|
|
42
|
+
// Get paths module if available, otherwise use defaults
|
|
43
|
+
let getSessionStatePath;
|
|
44
|
+
try {
|
|
45
|
+
({ getSessionStatePath } = require('./paths'));
|
|
46
|
+
} catch (e) {
|
|
47
|
+
// Fallback: find session-state.json manually
|
|
48
|
+
getSessionStatePath = rootDir => {
|
|
49
|
+
const possiblePaths = [
|
|
50
|
+
path.join(rootDir, 'docs', '09-agents', 'session-state.json'),
|
|
51
|
+
path.join(rootDir, '.agileflow', 'session-state.json'),
|
|
52
|
+
];
|
|
53
|
+
for (const p of possiblePaths) {
|
|
54
|
+
if (fs.existsSync(p)) return p;
|
|
55
|
+
}
|
|
56
|
+
return possiblePaths[0]; // Default to first path
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Find the project root directory
|
|
62
|
+
* @returns {string} Project root path
|
|
63
|
+
*/
|
|
64
|
+
function findProjectRoot() {
|
|
65
|
+
let dir = process.cwd();
|
|
66
|
+
while (dir !== '/') {
|
|
67
|
+
if (fs.existsSync(path.join(dir, '.agileflow'))) {
|
|
68
|
+
return dir;
|
|
69
|
+
}
|
|
70
|
+
if (fs.existsSync(path.join(dir, 'docs', '09-agents'))) {
|
|
71
|
+
return dir;
|
|
72
|
+
}
|
|
73
|
+
dir = path.dirname(dir);
|
|
74
|
+
}
|
|
75
|
+
return process.cwd();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Start a timer for hook execution
|
|
80
|
+
*
|
|
81
|
+
* @param {string} hookEvent - Hook event type (SessionStart, PreToolUse, PreCompact, Stop)
|
|
82
|
+
* @param {string} hookName - Name of the specific hook (welcome, archive, damage_control_bash)
|
|
83
|
+
* @returns {object} Timer object with start time and metadata
|
|
84
|
+
*/
|
|
85
|
+
function startHookTimer(hookEvent, hookName) {
|
|
86
|
+
return {
|
|
87
|
+
hookEvent,
|
|
88
|
+
hookName,
|
|
89
|
+
startTime: Date.now(),
|
|
90
|
+
startHrTime: process.hrtime.bigint(),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Record hook execution metrics to session-state.json
|
|
96
|
+
*
|
|
97
|
+
* @param {object} timer - Timer object from startHookTimer
|
|
98
|
+
* @param {string} status - Execution status ('success', 'error', 'blocked', 'timeout')
|
|
99
|
+
* @param {string} [errorMessage] - Optional error message if status is 'error'
|
|
100
|
+
* @param {object} [options] - Additional options
|
|
101
|
+
* @param {string} [options.rootDir] - Project root directory (auto-detected if not provided)
|
|
102
|
+
* @returns {object} Result with { ok, duration_ms, error? }
|
|
103
|
+
*/
|
|
104
|
+
function recordHookMetrics(timer, status, errorMessage = null, options = {}) {
|
|
105
|
+
const result = {
|
|
106
|
+
ok: false,
|
|
107
|
+
duration_ms: 0,
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
// Calculate duration
|
|
112
|
+
const endTime = Date.now();
|
|
113
|
+
const durationMs = endTime - timer.startTime;
|
|
114
|
+
result.duration_ms = durationMs;
|
|
115
|
+
|
|
116
|
+
// Find project root and session state file
|
|
117
|
+
const rootDir = options.rootDir || findProjectRoot();
|
|
118
|
+
const sessionStatePath = getSessionStatePath(rootDir);
|
|
119
|
+
|
|
120
|
+
// Ensure directory exists
|
|
121
|
+
const dir = path.dirname(sessionStatePath);
|
|
122
|
+
if (!fs.existsSync(dir)) {
|
|
123
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Read existing session state
|
|
127
|
+
let state = {};
|
|
128
|
+
if (fs.existsSync(sessionStatePath)) {
|
|
129
|
+
try {
|
|
130
|
+
state = JSON.parse(fs.readFileSync(sessionStatePath, 'utf8'));
|
|
131
|
+
} catch (e) {
|
|
132
|
+
// Invalid JSON - start fresh
|
|
133
|
+
state = {};
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Initialize hook_metrics if not present
|
|
138
|
+
if (!state.hook_metrics) {
|
|
139
|
+
state.hook_metrics = {
|
|
140
|
+
last_updated: null,
|
|
141
|
+
session_total_ms: 0,
|
|
142
|
+
hooks: {},
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Initialize event type if not present
|
|
147
|
+
if (!state.hook_metrics.hooks[timer.hookEvent]) {
|
|
148
|
+
state.hook_metrics.hooks[timer.hookEvent] = {};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Record the metric
|
|
152
|
+
const metric = {
|
|
153
|
+
duration_ms: durationMs,
|
|
154
|
+
status,
|
|
155
|
+
at: new Date().toISOString(),
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
if (errorMessage) {
|
|
159
|
+
metric.error = errorMessage;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
state.hook_metrics.hooks[timer.hookEvent][timer.hookName] = metric;
|
|
163
|
+
state.hook_metrics.last_updated = new Date().toISOString();
|
|
164
|
+
|
|
165
|
+
// Recalculate session total
|
|
166
|
+
let total = 0;
|
|
167
|
+
for (const eventHooks of Object.values(state.hook_metrics.hooks)) {
|
|
168
|
+
for (const hookMetric of Object.values(eventHooks)) {
|
|
169
|
+
total += hookMetric.duration_ms || 0;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
state.hook_metrics.session_total_ms = total;
|
|
173
|
+
|
|
174
|
+
// Write back
|
|
175
|
+
fs.writeFileSync(sessionStatePath, JSON.stringify(state, null, 2) + '\n');
|
|
176
|
+
|
|
177
|
+
result.ok = true;
|
|
178
|
+
} catch (e) {
|
|
179
|
+
result.error = e.message;
|
|
180
|
+
// Fail silently - metrics should never break hook execution
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return result;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Get current hook metrics from session-state.json
|
|
188
|
+
*
|
|
189
|
+
* @param {object} [options] - Options
|
|
190
|
+
* @param {string} [options.rootDir] - Project root directory (auto-detected if not provided)
|
|
191
|
+
* @returns {object} Hook metrics or empty object if not found
|
|
192
|
+
*/
|
|
193
|
+
function getHookMetrics(options = {}) {
|
|
194
|
+
try {
|
|
195
|
+
const rootDir = options.rootDir || findProjectRoot();
|
|
196
|
+
const sessionStatePath = getSessionStatePath(rootDir);
|
|
197
|
+
|
|
198
|
+
if (!fs.existsSync(sessionStatePath)) {
|
|
199
|
+
return { hooks: {}, session_total_ms: 0 };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const state = JSON.parse(fs.readFileSync(sessionStatePath, 'utf8'));
|
|
203
|
+
return state.hook_metrics || { hooks: {}, session_total_ms: 0 };
|
|
204
|
+
} catch (e) {
|
|
205
|
+
return { hooks: {}, session_total_ms: 0, error: e.message };
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Clear hook metrics (useful for testing or session reset)
|
|
211
|
+
*
|
|
212
|
+
* @param {object} [options] - Options
|
|
213
|
+
* @param {string} [options.rootDir] - Project root directory
|
|
214
|
+
* @returns {object} Result with { ok, error? }
|
|
215
|
+
*/
|
|
216
|
+
function clearHookMetrics(options = {}) {
|
|
217
|
+
try {
|
|
218
|
+
const rootDir = options.rootDir || findProjectRoot();
|
|
219
|
+
const sessionStatePath = getSessionStatePath(rootDir);
|
|
220
|
+
|
|
221
|
+
if (!fs.existsSync(sessionStatePath)) {
|
|
222
|
+
return { ok: true };
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const state = JSON.parse(fs.readFileSync(sessionStatePath, 'utf8'));
|
|
226
|
+
state.hook_metrics = {
|
|
227
|
+
last_updated: new Date().toISOString(),
|
|
228
|
+
session_total_ms: 0,
|
|
229
|
+
hooks: {},
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
fs.writeFileSync(sessionStatePath, JSON.stringify(state, null, 2) + '\n');
|
|
233
|
+
return { ok: true };
|
|
234
|
+
} catch (e) {
|
|
235
|
+
return { ok: false, error: e.message };
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Format hook metrics for display
|
|
241
|
+
*
|
|
242
|
+
* @param {object} metrics - Metrics from getHookMetrics()
|
|
243
|
+
* @returns {string} Formatted string for display
|
|
244
|
+
*/
|
|
245
|
+
function formatHookMetrics(metrics) {
|
|
246
|
+
if (!metrics || !metrics.hooks) {
|
|
247
|
+
return 'No hook metrics recorded';
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const lines = [];
|
|
251
|
+
lines.push(`Hook Metrics (total: ${metrics.session_total_ms}ms)`);
|
|
252
|
+
lines.push('─'.repeat(50));
|
|
253
|
+
|
|
254
|
+
for (const [event, hooks] of Object.entries(metrics.hooks)) {
|
|
255
|
+
lines.push(` ${event}:`);
|
|
256
|
+
for (const [name, data] of Object.entries(hooks)) {
|
|
257
|
+
const status = data.status === 'success' ? '✓' : data.status === 'error' ? '✗' : '?';
|
|
258
|
+
const error = data.error ? ` (${data.error})` : '';
|
|
259
|
+
lines.push(` ${status} ${name}: ${data.duration_ms}ms${error}`);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (metrics.last_updated) {
|
|
264
|
+
lines.push('');
|
|
265
|
+
lines.push(`Last updated: ${metrics.last_updated}`);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return lines.join('\n');
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Wrapper to time an async function and record metrics
|
|
273
|
+
*
|
|
274
|
+
* @param {string} hookEvent - Hook event type
|
|
275
|
+
* @param {string} hookName - Hook name
|
|
276
|
+
* @param {function} fn - Async function to time
|
|
277
|
+
* @param {object} [options] - Options passed to recordHookMetrics
|
|
278
|
+
* @returns {Promise<any>} Result of the function
|
|
279
|
+
*/
|
|
280
|
+
async function withHookMetrics(hookEvent, hookName, fn, options = {}) {
|
|
281
|
+
const timer = startHookTimer(hookEvent, hookName);
|
|
282
|
+
|
|
283
|
+
try {
|
|
284
|
+
const result = await fn();
|
|
285
|
+
recordHookMetrics(timer, 'success', null, options);
|
|
286
|
+
return result;
|
|
287
|
+
} catch (e) {
|
|
288
|
+
recordHookMetrics(timer, 'error', e.message, options);
|
|
289
|
+
throw e;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Wrapper to time a sync function and record metrics
|
|
295
|
+
*
|
|
296
|
+
* @param {string} hookEvent - Hook event type
|
|
297
|
+
* @param {string} hookName - Hook name
|
|
298
|
+
* @param {function} fn - Sync function to time
|
|
299
|
+
* @param {object} [options] - Options passed to recordHookMetrics
|
|
300
|
+
* @returns {any} Result of the function
|
|
301
|
+
*/
|
|
302
|
+
function withHookMetricsSync(hookEvent, hookName, fn, options = {}) {
|
|
303
|
+
const timer = startHookTimer(hookEvent, hookName);
|
|
304
|
+
|
|
305
|
+
try {
|
|
306
|
+
const result = fn();
|
|
307
|
+
recordHookMetrics(timer, 'success', null, options);
|
|
308
|
+
return result;
|
|
309
|
+
} catch (e) {
|
|
310
|
+
recordHookMetrics(timer, 'error', e.message, options);
|
|
311
|
+
throw e;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
module.exports = {
|
|
316
|
+
startHookTimer,
|
|
317
|
+
recordHookMetrics,
|
|
318
|
+
getHookMetrics,
|
|
319
|
+
clearHookMetrics,
|
|
320
|
+
formatHookMetrics,
|
|
321
|
+
withHookMetrics,
|
|
322
|
+
withHookMetricsSync,
|
|
323
|
+
findProjectRoot,
|
|
324
|
+
};
|