agileflow 3.0.1 → 3.0.2

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 (52) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/README.md +3 -3
  3. package/lib/api-server.js +3 -2
  4. package/lib/flag-detection.js +4 -2
  5. package/lib/git-operations.js +4 -2
  6. package/lib/process-executor.js +24 -9
  7. package/lib/skill-loader.js +11 -3
  8. package/package.json +1 -1
  9. package/scripts/agileflow-welcome.js +65 -25
  10. package/scripts/claude-tmux.sh +9 -3
  11. package/scripts/damage-control-multi-agent.js +14 -10
  12. package/scripts/lib/bus-utils.js +3 -1
  13. package/scripts/lib/configure-detect.js +12 -9
  14. package/scripts/lib/configure-features.js +12 -7
  15. package/scripts/lib/configure-repair.js +6 -5
  16. package/scripts/lib/context-formatter.js +13 -3
  17. package/scripts/lib/damage-control-utils.js +5 -1
  18. package/scripts/lib/lifecycle-detector.js +5 -3
  19. package/scripts/lib/process-cleanup.js +8 -4
  20. package/scripts/lib/scale-detector.js +47 -8
  21. package/scripts/lib/signal-detectors.js +117 -59
  22. package/scripts/lib/task-registry.js +5 -1
  23. package/scripts/lib/team-events.js +4 -4
  24. package/scripts/messaging-bridge.js +7 -1
  25. package/scripts/ralph-loop.js +10 -8
  26. package/scripts/smart-detect.js +32 -11
  27. package/scripts/team-manager.js +1 -1
  28. package/scripts/tmux-task-name.sh +75 -0
  29. package/scripts/tmux-task-watcher.sh +177 -0
  30. package/src/core/commands/babysit.md +75 -42
  31. package/src/core/commands/blockers.md +7 -7
  32. package/src/core/commands/configure.md +5 -36
  33. package/src/core/commands/discovery/brief.md +363 -0
  34. package/src/core/commands/discovery/new.md +395 -0
  35. package/src/core/commands/ideate/new.md +5 -5
  36. package/src/core/commands/logic/audit.md +5 -5
  37. package/src/core/commands/review.md +7 -1
  38. package/src/core/commands/rpi.md +61 -26
  39. package/src/core/commands/sprint.md +7 -6
  40. package/src/core/templates/product-brief.md +136 -0
  41. package/tools/cli/installers/ide/claude-code.js +67 -2
  42. package/src/core/agents/configuration/archival.md +0 -350
  43. package/src/core/agents/configuration/attribution.md +0 -343
  44. package/src/core/agents/configuration/ci.md +0 -1103
  45. package/src/core/agents/configuration/damage-control.md +0 -375
  46. package/src/core/agents/configuration/git-config.md +0 -537
  47. package/src/core/agents/configuration/hooks.md +0 -623
  48. package/src/core/agents/configuration/precompact.md +0 -302
  49. package/src/core/agents/configuration/status-line.md +0 -557
  50. package/src/core/agents/configuration/verify.md +0 -618
  51. package/src/core/agents/configuration-damage-control.md +0 -259
  52. package/src/core/agents/configuration-visual-e2e.md +0 -339
package/CHANGELOG.md CHANGED
@@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [3.0.2] - 2026-02-14
11
+
12
+ ### Added
13
+ - Automatic tmux window naming and configuration agent consolidation
14
+
10
15
  ## [3.0.1] - 2026-02-13
11
16
 
12
17
  ### Fixed
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/agileflow?color=brightgreen)](https://www.npmjs.com/package/agileflow)
6
6
  [![Commands](https://img.shields.io/badge/commands-93-blue)](docs/04-architecture/commands.md)
7
- [![Agents/Experts](https://img.shields.io/badge/agents%2Fexperts-47-orange)](docs/04-architecture/subagents.md)
7
+ [![Agents/Experts](https://img.shields.io/badge/agents%2Fexperts-45-orange)](docs/04-architecture/subagents.md)
8
8
  [![Skills](https://img.shields.io/badge/skills-dynamic-purple)](docs/04-architecture/skills.md)
9
9
 
10
10
  **AI-driven agile development for Claude Code, Cursor, Windsurf, OpenAI Codex CLI, and more.** Combining Scrum, Kanban, ADRs, and docs-as-code principles into one framework-agnostic system.
@@ -66,7 +66,7 @@ AgileFlow combines three proven methodologies:
66
66
  | Component | Count | Description |
67
67
  |-----------|-------|-------------|
68
68
  | [Commands](docs/04-architecture/commands.md) | 93 | Slash commands for agile workflows |
69
- | [Agents/Experts](docs/04-architecture/subagents.md) | 47 | Specialized agents with self-improving knowledge bases |
69
+ | [Agents/Experts](docs/04-architecture/subagents.md) | 45 | Specialized agents with self-improving knowledge bases |
70
70
  | [Skills](docs/04-architecture/skills.md) | Dynamic | Generated on-demand with `/agileflow:skill:create` |
71
71
 
72
72
  ---
@@ -77,7 +77,7 @@ Full documentation lives in [`docs/04-architecture/`](docs/04-architecture/):
77
77
 
78
78
  ### Reference
79
79
  - [Commands](docs/04-architecture/commands.md) - All 93 slash commands
80
- - [Agents/Experts](docs/04-architecture/subagents.md) - 47 specialized agents with self-improving knowledge
80
+ - [Agents/Experts](docs/04-architecture/subagents.md) - 45 specialized agents with self-improving knowledge
81
81
  - [Skills](docs/04-architecture/skills.md) - Dynamic skill generator with MCP integration
82
82
 
83
83
  ### Architecture
package/lib/api-server.js CHANGED
@@ -71,7 +71,7 @@ class ApiCache {
71
71
  * @returns {{ server: http.Server, options: Object, cache: ApiCache }}
72
72
  */
73
73
  function createApiServer(options = {}) {
74
- const port = options.port || DEFAULT_PORT;
74
+ const port = options.port != null ? options.port : DEFAULT_PORT;
75
75
  const host = options.host || DEFAULT_HOST;
76
76
  const rootDir = options.rootDir || getProjectRoot();
77
77
  const cacheTTL = options.cacheTTL || 2000;
@@ -238,7 +238,8 @@ function startApiServer(serverInstance) {
238
238
  });
239
239
 
240
240
  server.listen(port, host, () => {
241
- const url = `http://${host}:${port}`;
241
+ const actualPort = server.address().port;
242
+ const url = `http://${host}:${actualPort}`;
242
243
  console.log(`[AgileFlow API] Server running at ${url}`);
243
244
  console.log(`[AgileFlow API] Project root: ${options.rootDir}`);
244
245
  resolve({
@@ -219,7 +219,8 @@ function detectFromPs() {
219
219
  // Get command line for this PID
220
220
  let cmdline;
221
221
  const cmdResult = executeCommandSync('ps', ['-p', String(pid), '-o', 'args='], {
222
- timeout: 1000, fallback: null,
222
+ timeout: 1000,
223
+ fallback: null,
223
224
  });
224
225
  if (!cmdResult.ok || cmdResult.data === null) break;
225
226
  cmdline = cmdResult.data;
@@ -239,7 +240,8 @@ function detectFromPs() {
239
240
 
240
241
  // Get parent PID
241
242
  const ppidResult = executeCommandSync('ps', ['-p', String(pid), '-o', 'ppid='], {
242
- timeout: 1000, fallback: null,
243
+ timeout: 1000,
244
+ fallback: null,
243
245
  });
244
246
  if (!ppidResult.ok || ppidResult.data === null) break;
245
247
  pid = parseInt(ppidResult.data, 10);
@@ -176,12 +176,14 @@ function getSessionPhase(session) {
176
176
  try {
177
177
  const mainBranch = getMainBranch(sessionPath);
178
178
  const commitResult = git(['rev-list', '--count', `${mainBranch}..HEAD`], {
179
- cwd: sessionPath, fallback: '0',
179
+ cwd: sessionPath,
180
+ fallback: '0',
180
181
  });
181
182
  const commits = parseInt(commitResult.data, 10);
182
183
 
183
184
  const statusResult = git(['status', '--porcelain'], {
184
- cwd: sessionPath, fallback: '',
185
+ cwd: sessionPath,
186
+ fallback: '',
185
187
  });
186
188
  const status = statusResult.data;
187
189
 
@@ -91,13 +91,20 @@ function executeCommand(cmd, args = [], opts = {}) {
91
91
  let stderr = '';
92
92
  let timedOut = false;
93
93
 
94
- const timer = timeout > 0 ? setTimeout(() => {
95
- timedOut = true;
96
- proc.kill('SIGTERM');
97
- }, timeout) : null;
98
-
99
- proc.stdout.on('data', chunk => { stdout += chunk; });
100
- proc.stderr.on('data', chunk => { stderr += chunk; });
94
+ const timer =
95
+ timeout > 0
96
+ ? setTimeout(() => {
97
+ timedOut = true;
98
+ proc.kill('SIGTERM');
99
+ }, timeout)
100
+ : null;
101
+
102
+ proc.stdout.on('data', chunk => {
103
+ stdout += chunk;
104
+ });
105
+ proc.stderr.on('data', chunk => {
106
+ stderr += chunk;
107
+ });
101
108
 
102
109
  proc.on('error', err => {
103
110
  if (timer) clearTimeout(timer);
@@ -115,7 +122,11 @@ function executeCommand(cmd, args = [], opts = {}) {
115
122
  if (fallback !== undefined) {
116
123
  resolve({ ok: true, data: fallback });
117
124
  } else {
118
- resolve({ ok: false, error: `Command timed out after ${timeout}ms: ${cmd}`, exitCode: null });
125
+ resolve({
126
+ ok: false,
127
+ error: `Command timed out after ${timeout}ms: ${cmd}`,
128
+ exitCode: null,
129
+ });
119
130
  }
120
131
  return;
121
132
  }
@@ -124,7 +135,11 @@ function executeCommand(cmd, args = [], opts = {}) {
124
135
  if (fallback !== undefined) {
125
136
  resolve({ ok: true, data: fallback });
126
137
  } else {
127
- const result = { ok: false, error: `Command failed: ${cmd} ${args.join(' ')} (exit ${code})`, exitCode: code };
138
+ const result = {
139
+ ok: false,
140
+ error: `Command failed: ${cmd} ${args.join(' ')} (exit ${code})`,
141
+ exitCode: code,
142
+ };
128
143
  if (captureStderr) {
129
144
  result.stderr = trim ? stderr.trim() : stderr;
130
145
  }
@@ -57,13 +57,21 @@ function parseSkillFrontmatter(content) {
57
57
  let value = trimmed.substring(colonIndex + 1).trim();
58
58
 
59
59
  // Remove surrounding quotes
60
- if ((value.startsWith('"') && value.endsWith('"')) ||
61
- (value.startsWith("'") && value.endsWith("'"))) {
60
+ if (
61
+ (value.startsWith('"') && value.endsWith('"')) ||
62
+ (value.startsWith("'") && value.endsWith("'"))
63
+ ) {
62
64
  value = value.slice(1, -1);
63
65
  }
64
66
 
65
67
  // Type coercion for known fields
66
- if (key === 'version' || key === 'model' || key === 'category' || key === 'name' || key === 'type') {
68
+ if (
69
+ key === 'version' ||
70
+ key === 'model' ||
71
+ key === 'category' ||
72
+ key === 'name' ||
73
+ key === 'type'
74
+ ) {
67
75
  result.metadata[key] = value;
68
76
  } else if (value === 'true') {
69
77
  result.metadata[key] = true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agileflow",
3
- "version": "3.0.1",
3
+ "version": "3.0.2",
4
4
  "description": "AI-driven agile development system for Claude Code, Cursor, Windsurf, and more",
5
5
  "keywords": [
6
6
  "agile",
@@ -416,7 +416,8 @@ function checkParallelSessions(rootDir) {
416
416
 
417
417
  // PERFORMANCE: Single subprocess call instead of 3 (register + count + status)
418
418
  const fullStatusResult = executeCommandSync('node', [scriptPath, 'full-status'], {
419
- cwd: rootDir, fallback: null,
419
+ cwd: rootDir,
420
+ fallback: null,
420
421
  });
421
422
 
422
423
  if (fullStatusResult.data) {
@@ -443,7 +444,8 @@ function checkParallelSessions(rootDir) {
443
444
  if (!fullStatusResult.data) {
444
445
  // Fall back to individual calls if full-status not available (older version)
445
446
  const registerResult = executeCommandSync('node', [scriptPath, 'register'], {
446
- cwd: rootDir, fallback: null,
447
+ cwd: rootDir,
448
+ fallback: null,
447
449
  });
448
450
  if (registerResult.data) {
449
451
  try {
@@ -454,7 +456,8 @@ function checkParallelSessions(rootDir) {
454
456
  }
455
457
 
456
458
  const countResult = executeCommandSync('node', [scriptPath, 'count'], {
457
- cwd: rootDir, fallback: null,
459
+ cwd: rootDir,
460
+ fallback: null,
458
461
  });
459
462
  if (countResult.data) {
460
463
  try {
@@ -464,7 +467,8 @@ function checkParallelSessions(rootDir) {
464
467
  }
465
468
 
466
469
  const statusCmdResult = executeCommandSync('node', [scriptPath, 'status'], {
467
- cwd: rootDir, fallback: null,
470
+ cwd: rootDir,
471
+ fallback: null,
468
472
  });
469
473
  if (statusCmdResult.data) {
470
474
  try {
@@ -930,7 +934,9 @@ async function checkUpdates() {
930
934
  };
931
935
 
932
936
  let updateChecker;
933
- try { updateChecker = require('./check-update.js'); } catch (e) {}
937
+ try {
938
+ updateChecker = require('./check-update.js');
939
+ } catch (e) {}
934
940
  if (!updateChecker) return result;
935
941
 
936
942
  try {
@@ -1000,7 +1006,8 @@ function getChangelogEntries(version) {
1000
1006
  async function runAutoUpdate(rootDir, fromVersion, toVersion) {
1001
1007
  const runUpdate = () => {
1002
1008
  return executeCommandSync('npx', ['agileflow@latest', 'update', '--force'], {
1003
- cwd: rootDir, timeout: 120000,
1009
+ cwd: rootDir,
1010
+ timeout: 120000,
1004
1011
  });
1005
1012
  };
1006
1013
 
@@ -1528,17 +1535,26 @@ function formatTable(
1528
1535
  // Scale detection (EP-0033)
1529
1536
  if (scaleDetection && scaleDetection.scale) {
1530
1537
  const scaleColors = {
1531
- micro: c.cyan, small: c.teal, medium: c.mintGreen,
1532
- large: c.peach, enterprise: c.coral,
1538
+ micro: c.cyan,
1539
+ small: c.teal,
1540
+ medium: c.mintGreen,
1541
+ large: c.peach,
1542
+ enterprise: c.coral,
1533
1543
  };
1534
1544
  const scaleIcons = {
1535
- micro: '◦', small: '○', medium: '◎', large: '●', enterprise: '◉',
1545
+ micro: '◦',
1546
+ small: '○',
1547
+ medium: '◎',
1548
+ large: '●',
1549
+ enterprise: '◉',
1536
1550
  };
1537
1551
  const scale = scaleDetection.scale;
1538
1552
  const icon = scaleIcons[scale] || '◎';
1539
1553
  const label = scale.charAt(0).toUpperCase() + scale.slice(1);
1540
1554
  const cacheNote = scaleDetection.fromCache ? '' : ` (${scaleDetection.detection_ms}ms)`;
1541
- lines.push(row('Scale', `${icon} ${label}${cacheNote}`, c.lavender, scaleColors[scale] || c.dim));
1555
+ lines.push(
1556
+ row('Scale', `${icon} ${label}${cacheNote}`, c.lavender, scaleColors[scale] || c.dim)
1557
+ );
1542
1558
  }
1543
1559
 
1544
1560
  lines.push(divider());
@@ -1634,13 +1650,20 @@ async function main() {
1634
1650
  // Scale detection not available
1635
1651
  }
1636
1652
 
1637
- const scaleRecommendations = earlyScale ? (() => {
1638
- try { return require('./lib/scale-detector').getScaleRecommendations(earlyScale.scale); } catch { return null; }
1639
- })() : null;
1653
+ const scaleRecommendations = earlyScale
1654
+ ? (() => {
1655
+ try {
1656
+ return require('./lib/scale-detector').getScaleRecommendations(earlyScale.scale);
1657
+ } catch {
1658
+ return null;
1659
+ }
1660
+ })()
1661
+ : null;
1640
1662
 
1641
- const archival = (scaleRecommendations && scaleRecommendations.skipArchival)
1642
- ? { ran: false, threshold: 0, archived: 0, remaining: 0, skippedByScale: true }
1643
- : runArchival(rootDir, cache);
1663
+ const archival =
1664
+ scaleRecommendations && scaleRecommendations.skipArchival
1665
+ ? { ran: false, threshold: 0, archived: 0, remaining: 0, skippedByScale: true }
1666
+ : runArchival(rootDir, cache);
1644
1667
  const session = clearActiveCommands(rootDir, cache);
1645
1668
  const precompact = checkPreCompact(rootDir, cache);
1646
1669
  const parallelSessions = checkParallelSessions(rootDir);
@@ -1654,7 +1677,9 @@ async function main() {
1654
1677
 
1655
1678
  // Agent Teams feature flag detection
1656
1679
  let featureFlags;
1657
- try { featureFlags = require('../lib/feature-flags'); } catch (e) {}
1680
+ try {
1681
+ featureFlags = require('../lib/feature-flags');
1682
+ } catch (e) {}
1658
1683
  let agentTeamsInfo = {};
1659
1684
  if (featureFlags) {
1660
1685
  try {
@@ -1776,14 +1801,18 @@ async function main() {
1776
1801
 
1777
1802
  // Mark current version as seen to track for next update
1778
1803
  let updateChecker;
1779
- try { updateChecker = require('./check-update.js'); } catch (e) {}
1804
+ try {
1805
+ updateChecker = require('./check-update.js');
1806
+ } catch (e) {}
1780
1807
  if (freshUpdateInfo.justUpdated && updateChecker) {
1781
1808
  updateChecker.markVersionSeen(info.version);
1782
1809
  }
1783
1810
  } else {
1784
1811
  // Mark current version as seen (for "just updated" case)
1785
1812
  let updateChecker;
1786
- try { updateChecker = require('./check-update.js'); } catch (e) {}
1813
+ try {
1814
+ updateChecker = require('./check-update.js');
1815
+ } catch (e) {}
1787
1816
  if (updateChecker) {
1788
1817
  updateChecker.markVersionSeen(info.version);
1789
1818
  }
@@ -1870,7 +1899,8 @@ async function main() {
1870
1899
  // Check for forgotten sessions with uncommitted changes, stale sessions, orphaned entries
1871
1900
  try {
1872
1901
  const healthResult = executeCommandSync('node', [SESSION_MANAGER_PATH, 'health'], {
1873
- timeout: 10000, fallback: null,
1902
+ timeout: 10000,
1903
+ fallback: null,
1874
1904
  });
1875
1905
 
1876
1906
  if (healthResult.data) {
@@ -1922,7 +1952,9 @@ async function main() {
1922
1952
  // === DUPLICATE CLAUDE PROCESS DETECTION ===
1923
1953
  // Check for multiple Claude processes in the same working directory
1924
1954
  let processCleanup;
1925
- try { processCleanup = require('./lib/process-cleanup.js'); } catch (e) {}
1955
+ try {
1956
+ processCleanup = require('./lib/process-cleanup.js');
1957
+ } catch (e) {}
1926
1958
  if (processCleanup) {
1927
1959
  try {
1928
1960
  // Auto-kill is explicitly opt-in at runtime.
@@ -1976,7 +2008,9 @@ async function main() {
1976
2008
 
1977
2009
  // Story claiming: cleanup stale claims and show warnings
1978
2010
  let storyClaiming;
1979
- try { storyClaiming = require('./lib/story-claiming.js'); } catch (e) {}
2011
+ try {
2012
+ storyClaiming = require('./lib/story-claiming.js');
2013
+ } catch (e) {}
1980
2014
  if (storyClaiming) {
1981
2015
  try {
1982
2016
  // Clean up stale claims (dead PIDs, expired TTL)
@@ -2003,7 +2037,9 @@ async function main() {
2003
2037
 
2004
2038
  // File tracking: cleanup stale touches and show overlap warnings
2005
2039
  let fileTracking;
2006
- try { fileTracking = require('./lib/file-tracking.js'); } catch (e) {}
2040
+ try {
2041
+ fileTracking = require('./lib/file-tracking.js');
2042
+ } catch (e) {}
2007
2043
  if (fileTracking) {
2008
2044
  try {
2009
2045
  // Clean up stale file touches (dead PIDs, expired TTL)
@@ -2028,7 +2064,9 @@ async function main() {
2028
2064
 
2029
2065
  // Epic completion check: auto-complete epics where all stories are done
2030
2066
  let storyStateMachine;
2031
- try { storyStateMachine = require('./lib/story-state-machine.js'); } catch (e) {}
2067
+ try {
2068
+ storyStateMachine = require('./lib/story-state-machine.js');
2069
+ } catch (e) {}
2032
2070
  if (storyStateMachine && cache.status) {
2033
2071
  try {
2034
2072
  const statusPath = getStatusPath(rootDir);
@@ -2058,7 +2096,9 @@ async function main() {
2058
2096
 
2059
2097
  // Ideation sync: mark ideas as implemented when linked epics complete
2060
2098
  let syncIdeationStatus;
2061
- try { syncIdeationStatus = require('./lib/sync-ideation-status.js'); } catch (e) {}
2099
+ try {
2100
+ syncIdeationStatus = require('./lib/sync-ideation-status.js');
2101
+ } catch (e) {}
2062
2102
  if (syncIdeationStatus) {
2063
2103
  try {
2064
2104
  const syncResult = syncIdeationStatus.syncImplementedIdeas(rootDir);
@@ -165,6 +165,9 @@ configure_tmux_session() {
165
165
  # Enable mouse support
166
166
  tmux set-option -t "$target_session" mouse on
167
167
 
168
+ # Automatically renumber windows when one is closed (no gaps)
169
+ tmux set-option -t "$target_session" renumber-windows on
170
+
168
171
  # Fix colors - proper terminal support
169
172
  tmux set-option -t "$target_session" default-terminal "xterm-256color"
170
173
  tmux set-option -t "$target_session" -ga terminal-overrides ",xterm-256color:Tc"
@@ -245,7 +248,8 @@ configure_tmux_session() {
245
248
 
246
249
  # ─── Session Creation Keybindings ──────────────────────────────────────────
247
250
  # Alt+s to create a new Claude window (starts fresh, future re-runs in same pane resume)
248
- tmux bind-key -n M-s run-shell "tmux new-window -n claude -c '#{pane_current_path}' && tmux send-keys '\"\$AGILEFLOW_SCRIPTS/claude-smart.sh\" --fresh \$CLAUDE_SESSION_FLAGS' Enter"
251
+ # Window gets sequential name (claude-2, claude-3, ...) so windows are distinguishable
252
+ tmux bind-key -n M-s run-shell "N=\$(( \$(tmux list-windows -F '#{window_name}' 2>/dev/null | grep -c '^claude') + 1 )); tmux new-window -n \"claude-\$N\" -c '#{pane_current_path}' && tmux send-keys '\"\$AGILEFLOW_SCRIPTS/claude-smart.sh\" --fresh \$CLAUDE_SESSION_FLAGS' Enter"
249
253
 
250
254
  # ─── Freeze Recovery Keybindings ───────────────────────────────────────────
251
255
  # Alt+k to send Ctrl+C twice (soft interrupt for frozen processes)
@@ -253,7 +257,7 @@ configure_tmux_session() {
253
257
 
254
258
  # ─── Help Panel ──────────────────────────────────────────────────────────
255
259
  # Alt+h to show all Alt keybindings in a popup
256
- tmux bind-key -n M-h display-popup -E -w 52 -h 26 "\
260
+ tmux bind-key -n M-h display-popup -E -w 52 -h 30 "\
257
261
  printf '\\n';\
258
262
  printf ' \\033[1;38;5;208mSESSIONS\\033[0m\\n';\
259
263
  printf ' Alt+s New Claude session\\n';\
@@ -272,7 +276,9 @@ configure_tmux_session() {
272
276
  printf ' Alt+v Split top / bottom\\n';\
273
277
  printf ' Alt+arrows Navigate panes\\n';\
274
278
  printf ' Alt+z Zoom / unzoom\\n';\
275
- printf ' Alt+x Close pane\\n';\
279
+ printf ' Alt+x Close pane (confirm)\\n';\
280
+ printf ' Alt+K Kill pane (no confirm)\\n';\
281
+ printf ' Alt+R Restart pane\\n';\
276
282
  printf '\\n';\
277
283
  printf ' \\033[1;38;5;208mOTHER\\033[0m\\n';\
278
284
  printf ' Alt+[ Scroll mode\\n';\
@@ -48,8 +48,12 @@ if (!utils || typeof utils.runDamageControlHook !== 'function') {
48
48
 
49
49
  // Tools this hook handles
50
50
  const MULTI_AGENT_TOOLS = [
51
- 'TeamCreate', 'TeamDelete',
52
- 'TaskCreate', 'TaskUpdate', 'TaskGet', 'TaskList',
51
+ 'TeamCreate',
52
+ 'TeamDelete',
53
+ 'TaskCreate',
54
+ 'TaskUpdate',
55
+ 'TaskGet',
56
+ 'TaskList',
53
57
  'SendMessage',
54
58
  ];
55
59
 
@@ -65,10 +69,10 @@ const MAX_MESSAGE_SIZE = 10240;
65
69
  // Blocked patterns in SendMessage content
66
70
  const BLOCKED_MESSAGE_PATTERNS = [
67
71
  // Command injection attempts
68
- /\$\{.*\}/, // Template injection ${...}
69
- /`[^`]*`/, // Backtick execution
70
- /\bexec\s*\(/, // exec() calls
71
- /\beval\s*\(/, // eval() calls
72
+ /\$\{.*\}/, // Template injection ${...}
73
+ /`[^`]*`/, // Backtick execution
74
+ /\bexec\s*\(/, // exec() calls
75
+ /\beval\s*\(/, // eval() calls
72
76
  // Dangerous git operations
73
77
  /\bgit\s+push\s+--force\b/i,
74
78
  /\bgit\s+reset\s+--hard\b/i,
@@ -143,9 +147,9 @@ function validateTaskOperation(input) {
143
147
  // Check for secrets in task descriptions
144
148
  const secretPatterns = [
145
149
  /\b(?:API_KEY|SECRET|PASSWORD|TOKEN|CREDENTIALS)\s*[:=]\s*\S+/i,
146
- /\bsk-[a-zA-Z0-9]{20,}/, // API keys starting with sk-
147
- /\bghp_[a-zA-Z0-9]{36}/, // GitHub personal access tokens
148
- /\bnpm_[a-zA-Z0-9]{36}/, // npm tokens
150
+ /\bsk-[a-zA-Z0-9]{20,}/, // API keys starting with sk-
151
+ /\bghp_[a-zA-Z0-9]{36}/, // GitHub personal access tokens
152
+ /\bnpm_[a-zA-Z0-9]{36}/, // npm tokens
149
153
  ];
150
154
 
151
155
  for (const pattern of secretPatterns) {
@@ -163,7 +167,7 @@ function validateTaskOperation(input) {
163
167
 
164
168
  try {
165
169
  utils.runDamageControlHook({
166
- getInputValue: (input) => {
170
+ getInputValue: input => {
167
171
  // Check if this is a multi-agent tool
168
172
  const toolName = input.tool_name || '';
169
173
  if (!MULTI_AGENT_TOOLS.includes(toolName)) {
@@ -341,7 +341,9 @@ function formatLogStats(stats) {
341
341
  lines.push('Archives:');
342
342
  if (stats.archives && stats.archives.length > 0) {
343
343
  for (const archive of stats.archives) {
344
- lines.push(` ${archive.filename}: ${archive.lineCount} lines (${Math.round(archive.size / 1024)} KB)`);
344
+ lines.push(
345
+ ` ${archive.filename}: ${archive.lineCount} lines (${Math.round(archive.size / 1024)} KB)`
346
+ );
345
347
  }
346
348
  }
347
349
 
@@ -107,10 +107,15 @@ function detectConfig(version) {
107
107
  // Git detection
108
108
  if (fs.existsSync('.git')) {
109
109
  status.git.initialized = true;
110
- status.git.remote = tryOptional(() => execFileSync('git', ['remote', 'get-url', 'origin'], {
111
- encoding: 'utf8',
112
- stdio: ['pipe', 'pipe', 'pipe'],
113
- }).trim(), 'git remote') ?? null;
110
+ status.git.remote =
111
+ tryOptional(
112
+ () =>
113
+ execFileSync('git', ['remote', 'get-url', 'origin'], {
114
+ encoding: 'utf8',
115
+ stdio: ['pipe', 'pipe', 'pipe'],
116
+ }).trim(),
117
+ 'git remote'
118
+ ) ?? null;
114
119
  }
115
120
 
116
121
  // Settings file detection
@@ -312,17 +317,15 @@ function detectMetadata(status, version) {
312
317
 
313
318
  // Content-based outdated detection
314
319
  const featureConfig = FEATURES[statusKey];
315
- const scriptsToCheck = featureConfig?.scripts
316
- || (featureConfig?.script ? [featureConfig.script] : []);
320
+ const scriptsToCheck =
321
+ featureConfig?.scripts || (featureConfig?.script ? [featureConfig.script] : []);
317
322
 
318
323
  if (scriptsToCheck.length > 0 && packageScriptDir) {
319
324
  // Compare installed scripts against package source
320
325
  let isOutdated = false;
321
326
  for (const scriptName of scriptsToCheck) {
322
327
  const packageScript = path.join(packageScriptDir, scriptName);
323
- const installedScript = path.join(
324
- process.cwd(), '.agileflow', 'scripts', scriptName
325
- );
328
+ const installedScript = path.join(process.cwd(), '.agileflow', 'scripts', scriptName);
326
329
  const packageHash = hashFile(packageScript);
327
330
  const installedHash = hashFile(installedScript);
328
331
 
@@ -55,7 +55,8 @@ const FEATURES = {
55
55
  },
56
56
  claudeflags: {
57
57
  metadataOnly: false,
58
- description: 'Default flags for Claude CLI (sets permissions.defaultMode in .claude/settings.json)',
58
+ description:
59
+ 'Default flags for Claude CLI (sets permissions.defaultMode in .claude/settings.json)',
59
60
  },
60
61
  agentteams: {
61
62
  metadataOnly: false,
@@ -459,12 +460,16 @@ function enableFeature(feature, options = {}, version) {
459
460
  : null;
460
461
  writeJSON('.claude/settings.json', settings);
461
462
  updateMetadata(
462
- { features: { [feature]: {
463
- enabled: true,
464
- version,
465
- ...(contentHash ? { contentHash } : {}),
466
- at: new Date().toISOString(),
467
- } } },
463
+ {
464
+ features: {
465
+ [feature]: {
466
+ enabled: true,
467
+ version,
468
+ ...(contentHash ? { contentHash } : {}),
469
+ at: new Date().toISOString(),
470
+ },
471
+ },
472
+ },
468
473
  version
469
474
  );
470
475
  updateGitignore();
@@ -116,11 +116,12 @@ function listScripts() {
116
116
  // Check if modified
117
117
  let isModified = false;
118
118
  if (exists && fileIndex?.files?.[`scripts/${script}`]) {
119
- isModified = tryOptional(() => {
120
- const currentHash = sha256(fs.readFileSync(scriptPath));
121
- const indexHash = fileIndex.files[`scripts/${script}`].sha256;
122
- return currentHash !== indexHash;
123
- }, 'hash check') ?? false;
119
+ isModified =
120
+ tryOptional(() => {
121
+ const currentHash = sha256(fs.readFileSync(scriptPath));
122
+ const indexHash = fileIndex.files[`scripts/${script}`].sha256;
123
+ return currentHash !== indexHash;
124
+ }, 'hash check') ?? false;
124
125
  }
125
126
 
126
127
  // Print status
@@ -323,7 +323,11 @@ function generateSummary(prefetched = null, options = {}) {
323
323
 
324
324
  // Scale indicator (EP-0033)
325
325
  let scaleDetectorSummary;
326
- try { scaleDetectorSummary = require('./scale-detector'); } catch { /* not available */ }
326
+ try {
327
+ scaleDetectorSummary = require('./scale-detector');
328
+ } catch {
329
+ /* not available */
330
+ }
327
331
  if (scaleDetectorSummary) {
328
332
  try {
329
333
  const scaleResult = scaleDetectorSummary.detectScale({
@@ -503,7 +507,11 @@ function generateFullContent(prefetched = null, options = {}) {
503
507
 
504
508
  // SCALE DETECTION (EP-0033)
505
509
  let scaleDetector;
506
- try { scaleDetector = require('./scale-detector'); } catch { /* not available */ }
510
+ try {
511
+ scaleDetector = require('./scale-detector');
512
+ } catch {
513
+ /* not available */
514
+ }
507
515
  if (scaleDetector) {
508
516
  try {
509
517
  const scaleResult = scaleDetector.detectScale({
@@ -886,7 +894,9 @@ function generateRemainingContent(prefetched, options = {}) {
886
894
 
887
895
  // Show auto-enabled modes
888
896
  const modes = auto_enabled || {};
889
- const enabledModes = Object.entries(modes).filter(([, v]) => v).map(([k]) => k.replace('_', ' '));
897
+ const enabledModes = Object.entries(modes)
898
+ .filter(([, v]) => v)
899
+ .map(([k]) => k.replace('_', ' '));
890
900
  if (enabledModes.length > 0) {
891
901
  content += `\n${C.mintGreen}Auto-enabled:${C.reset} ${enabledModes.join(', ')}\n`;
892
902
  }
@@ -96,7 +96,11 @@ function loadPatterns(projectRoot, parseYAML, defaultConfig = {}) {
96
96
  const stat = fs.statSync(fullPath);
97
97
  const mtime = stat.mtimeMs;
98
98
 
99
- if (_patternCache.filePath === fullPath && _patternCache.mtime === mtime && _patternCache.config) {
99
+ if (
100
+ _patternCache.filePath === fullPath &&
101
+ _patternCache.mtime === mtime &&
102
+ _patternCache.config
103
+ ) {
100
104
  return _patternCache.config;
101
105
  }
102
106