agileflow 2.83.0 → 2.84.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 CHANGED
@@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [2.84.1] - 2026-01-11
11
+
12
+ ### Fixed
13
+ - Status line and welcome table formatting fixes
14
+
15
+ ## [2.84.0] - 2026-01-11
16
+
17
+ ### Added
18
+ - File awareness, smart merge, and simplified auto-update
19
+
10
20
  ## [2.83.0] - 2026-01-10
11
21
 
12
22
  ### Changed
package/README.md CHANGED
@@ -144,7 +144,7 @@ docs/
144
144
 
145
145
  ## Online Documentation
146
146
 
147
- Visit [projectquestorg.com](https://projectquestorg.com) for the full documentation site.
147
+ Visit [docs.agileflow.projectquestorg.com](https://docs.agileflow.projectquestorg.com) for the full documentation site.
148
148
 
149
149
  ---
150
150
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agileflow",
3
- "version": "2.83.0",
3
+ "version": "2.84.1",
4
4
  "description": "AI-driven agile development system for Claude Code, Cursor, Windsurf, and more",
5
5
  "keywords": [
6
6
  "agile",
@@ -788,16 +788,14 @@ function enableFeature(feature, options = {}) {
788
788
 
789
789
  // Handle autoupdate (metadata only, no hooks needed)
790
790
  if (feature === 'autoupdate') {
791
- const frequency = options.checkFrequency || 'daily';
792
791
  updateMetadata({
793
792
  updates: {
794
793
  autoUpdate: true,
795
- checkFrequency: frequency,
796
794
  showChangelog: true,
797
795
  },
798
796
  });
799
- success(`Auto-update enabled (check frequency: ${frequency})`);
800
- info('AgileFlow will automatically update on session start');
797
+ success('Auto-update enabled');
798
+ info('AgileFlow will check for updates every session and update automatically');
801
799
  return true; // Skip settings.json write for this feature
802
800
  }
803
801
 
@@ -385,11 +385,11 @@ PYTHON
385
385
  SESSION_COLOR="$SESSION_GREEN" # Light green - plenty of time
386
386
  fi
387
387
 
388
- # Build compact display: "2h15m" or "45m"
388
+ # Build compact display: "2h15m" or "45m" (hourglass indicates remaining time)
389
389
  if [ "$RH" -gt 0 ]; then
390
- SESSION_DISPLAY="${SESSION_COLOR}⏱${RH}h${RM}m${RESET}"
390
+ SESSION_DISPLAY="${SESSION_COLOR}⧗${RH}h${RM}m${RESET}"
391
391
  else
392
- SESSION_DISPLAY="${SESSION_COLOR}⏱${RM}m${RESET}"
392
+ SESSION_DISPLAY="${SESSION_COLOR}⧗${RM}m${RESET}"
393
393
  fi
394
394
  fi
395
395
  fi
@@ -520,6 +520,47 @@ if git rev-parse --git-dir > /dev/null 2>&1; then
520
520
  fi
521
521
  fi
522
522
 
523
+ # ============================================================================
524
+ # Session Info (Multi-session awareness)
525
+ # ============================================================================
526
+ # Show current session if in a non-main session
527
+ SESSION_INFO=""
528
+ SHOW_SESSION=true # New component - default enabled
529
+
530
+ # Check component setting
531
+ if [ "$COMPONENTS" != "null" ] && [ -n "$COMPONENTS" ]; then
532
+ SHOW_SESSION=$(echo "$COMPONENTS" | jq -r '.session | if . == null then true else . end')
533
+ fi
534
+
535
+ if [ "$SHOW_SESSION" = "true" ]; then
536
+ REGISTRY_FILE=".agileflow/sessions/registry.json"
537
+ if [ -f "$REGISTRY_FILE" ]; then
538
+ # Get current working directory
539
+ CWD=$(pwd)
540
+
541
+ # Find session matching current directory
542
+ SESSION_DATA=$(jq -r --arg cwd "$CWD" '
543
+ .sessions | to_entries[] | select(.value.path == $cwd) |
544
+ {id: .key, nickname: .value.nickname, is_main: .value.is_main}
545
+ ' "$REGISTRY_FILE" 2>/dev/null)
546
+
547
+ if [ -n "$SESSION_DATA" ]; then
548
+ SESSION_NUM=$(echo "$SESSION_DATA" | jq -r '.id')
549
+ SESSION_NICK=$(echo "$SESSION_DATA" | jq -r '.nickname // empty')
550
+ IS_MAIN=$(echo "$SESSION_DATA" | jq -r '.is_main // false')
551
+
552
+ # Only show for non-main sessions
553
+ if [ "$IS_MAIN" != "true" ] && [ -n "$SESSION_NUM" ]; then
554
+ if [ -n "$SESSION_NICK" ] && [ "$SESSION_NICK" != "null" ]; then
555
+ SESSION_INFO="${DIM}[${RESET}${MAGENTA}S${SESSION_NUM}${RESET}${DIM}:${RESET}${SESSION_NICK}${DIM}]${RESET}"
556
+ else
557
+ SESSION_INFO="${DIM}[${RESET}${MAGENTA}S${SESSION_NUM}${RESET}${DIM}]${RESET}"
558
+ fi
559
+ fi
560
+ fi
561
+ fi
562
+ fi
563
+
523
564
  # ============================================================================
524
565
  # Build Status Line
525
566
  # ============================================================================
@@ -533,6 +574,12 @@ if [ "$SHOW_AGILEFLOW" = "true" ] && [ -n "$AGILEFLOW_DISPLAY" ]; then
533
574
  OUTPUT="${AGILEFLOW_DISPLAY}"
534
575
  fi
535
576
 
577
+ # Session info (if in a non-main session)
578
+ if [ "$SHOW_SESSION" = "true" ] && [ -n "$SESSION_INFO" ]; then
579
+ [ -n "$OUTPUT" ] && OUTPUT="${OUTPUT}${SEP}"
580
+ OUTPUT="${OUTPUT}${SESSION_INFO}"
581
+ fi
582
+
536
583
  # Model with subtle styling (if enabled and available)
537
584
  if [ "$SHOW_MODEL" = "true" ] && [ -n "$MODEL_DISPLAY" ]; then
538
585
  [ -n "$OUTPUT" ] && OUTPUT="${OUTPUT}${SEP}"
@@ -22,6 +22,22 @@ const { getProjectRoot } = require('../lib/paths');
22
22
  // Session manager path (relative to script location)
23
23
  const SESSION_MANAGER_PATH = path.join(__dirname, 'session-manager.js');
24
24
 
25
+ // Story claiming module
26
+ let storyClaiming;
27
+ try {
28
+ storyClaiming = require('./lib/story-claiming.js');
29
+ } catch (e) {
30
+ // Story claiming not available
31
+ }
32
+
33
+ // File tracking module
34
+ let fileTracking;
35
+ try {
36
+ fileTracking = require('./lib/file-tracking.js');
37
+ } catch (e) {
38
+ // File tracking not available
39
+ }
40
+
25
41
  // Update checker module
26
42
  let updateChecker;
27
43
  try {
@@ -481,14 +497,18 @@ function getChangelogEntries(version) {
481
497
  async function runAutoUpdate(rootDir) {
482
498
  try {
483
499
  console.log(`${c.skyBlue}Updating AgileFlow...${c.reset}`);
484
- execSync('npx agileflow update', {
500
+ // Use --force to skip prompts for non-interactive auto-update
501
+ execSync('npx agileflow@latest update --force', {
485
502
  cwd: rootDir,
486
503
  encoding: 'utf8',
487
504
  stdio: 'inherit',
505
+ timeout: 120000, // 2 minute timeout
488
506
  });
507
+ console.log(`${c.mintGreen}Update complete!${c.reset}`);
489
508
  return true;
490
509
  } catch (e) {
491
- console.log(`${c.peach}Auto-update failed. Run manually: npx agileflow update${c.reset}`);
510
+ console.log(`${c.peach}Auto-update failed: ${e.message}${c.reset}`);
511
+ console.log(`${c.dim}Run manually: npx agileflow update${c.reset}`);
492
512
  return false;
493
513
  }
494
514
  }
@@ -655,8 +675,8 @@ function formatTable(
655
675
  expertise = {},
656
676
  damageControl = {}
657
677
  ) {
658
- const W = 58; // inner width
659
- const R = W - 24; // right column width (34 chars)
678
+ const W = 58; // inner width (total table = W + 2 = 60)
679
+ const R = W - 25; // right column width (33 chars) to match total of 60
660
680
  const lines = [];
661
681
 
662
682
  // Helper to create a row (auto-truncates right content to fit)
@@ -668,16 +688,26 @@ function formatTable(
668
688
  };
669
689
 
670
690
  // Helper for full-width row (spans both columns)
691
+ // Content width = W - 2 (for the two spaces after │ and before │)
671
692
  const fullRow = (content, color = '') => {
672
693
  const contentStr = `${color}${content}${color ? c.reset : ''}`;
673
- return `${c.dim}${box.v}${c.reset} ${pad(contentStr, W - 1)} ${c.dim}${box.v}${c.reset}`;
694
+ return `${c.dim}${box.v}${c.reset} ${pad(contentStr, W - 2)} ${c.dim}${box.v}${c.reset}`;
674
695
  };
675
696
 
697
+ // Two-column dividers: ├ + 22 dashes + ┼ + 35 dashes + ┤ = 60 total
676
698
  const divider = () =>
677
- `${c.dim}${box.lT}${box.h.repeat(22)}${box.cross}${box.h.repeat(W - 22)}${box.rT}${c.reset}`;
699
+ `${c.dim}${box.lT}${box.h.repeat(22)}${box.cross}${box.h.repeat(W - 23)}${box.rT}${c.reset}`;
700
+ // Full-width divider: ├ + 58 dashes + ┤ = 60 total
678
701
  const fullDivider = () => `${c.dim}${box.lT}${box.h.repeat(W)}${box.rT}${c.reset}`;
679
- const topBorder = `${c.dim}${box.tl}${box.h.repeat(22)}${box.tT}${box.h.repeat(W - 22)}${box.tr}${c.reset}`;
680
- const bottomBorder = `${c.dim}${box.bl}${box.h.repeat(22)}${box.bT}${box.h.repeat(W - 22)}${box.br}${c.reset}`;
702
+ // Transition: full-width TO two-column
703
+ const splitDivider = () =>
704
+ `${c.dim}${box.lT}${box.h.repeat(22)}${box.tT}${box.h.repeat(W - 23)}${box.rT}${c.reset}`;
705
+ // Transition: two-column TO full-width
706
+ const mergeDivider = () =>
707
+ `${c.dim}${box.lT}${box.h.repeat(22)}${box.bT}${box.h.repeat(W - 23)}${box.rT}${c.reset}`;
708
+ // Borders
709
+ const topBorder = `${c.dim}${box.tl}${box.h.repeat(W)}${box.tr}${c.reset}`;
710
+ const bottomBorder = `${c.dim}${box.bl}${box.h.repeat(22)}${box.bT}${box.h.repeat(W - 23)}${box.br}${c.reset}`;
681
711
 
682
712
  // Header with version and optional update indicator
683
713
  // Use vibrant colors for branch
@@ -705,7 +735,7 @@ function formatTable(
705
735
  : info.branch;
706
736
 
707
737
  const header = `${c.brand}${c.bold}agileflow${c.reset} ${c.dim}${versionStr}${c.reset} ${branchColor}${branchDisplay}${c.reset} ${c.dim}(${info.commit})${c.reset}`;
708
- const headerLine = `${c.dim}${box.v}${c.reset} ${pad(header, W - 1)} ${c.dim}${box.v}${c.reset}`;
738
+ const headerLine = `${c.dim}${box.v}${c.reset} ${pad(header, W - 2)} ${c.dim}${box.v}${c.reset}`;
709
739
 
710
740
  lines.push(topBorder);
711
741
  lines.push(headerLine);
@@ -722,22 +752,26 @@ function formatTable(
722
752
  lines.push(fullRow(` Run: ${c.skyBlue}npx agileflow update${c.reset}`, ''));
723
753
  }
724
754
 
725
- // Show "just updated" changelog
726
- if (updateInfo.justUpdated && updateInfo.changelog && updateInfo.changelog.length > 0) {
755
+ // Always show "What's new" section with current version changelog
756
+ // Get changelog entries for current version (even if not just updated)
757
+ const changelogEntries = updateInfo.changelog && updateInfo.changelog.length > 0
758
+ ? updateInfo.changelog
759
+ : getChangelogEntries(info.version);
760
+
761
+ if (changelogEntries && changelogEntries.length > 0) {
727
762
  lines.push(fullDivider());
728
- lines.push(
729
- fullRow(
730
- `${c.mintGreen}✨${c.reset} What's new in ${c.softGold}v${info.version}${c.reset}:`,
731
- ''
732
- )
733
- );
734
- for (const entry of updateInfo.changelog.slice(0, 2)) {
763
+ const headerText = updateInfo.justUpdated
764
+ ? `${c.mintGreen}✨${c.reset} Just updated to ${c.softGold}v${info.version}${c.reset}:`
765
+ : `${c.teal}📋${c.reset} What's new in ${c.softGold}v${info.version}${c.reset}:`;
766
+ lines.push(fullRow(headerText, ''));
767
+ for (const entry of changelogEntries.slice(0, 2)) {
735
768
  lines.push(fullRow(` ${c.teal}•${c.reset} ${truncate(entry, W - 6)}`, ''));
736
769
  }
737
770
  lines.push(fullRow(` Run ${c.skyBlue}/agileflow:whats-new${c.reset} for full changelog`, ''));
738
771
  }
739
772
 
740
- lines.push(divider());
773
+ // Transition from full-width sections to two-column stories section
774
+ lines.push(splitDivider());
741
775
 
742
776
  // Stories section (always colorful labels like obtain-context)
743
777
  lines.push(
@@ -976,6 +1010,56 @@ async function main() {
976
1010
  `${c.slate} Run ${c.skyBlue}/agileflow:session:new${c.reset}${c.slate} to create isolated workspace.${c.reset}`
977
1011
  );
978
1012
  }
1013
+
1014
+ // Story claiming: cleanup stale claims and show warnings
1015
+ if (storyClaiming) {
1016
+ try {
1017
+ // Clean up stale claims (dead PIDs, expired TTL)
1018
+ const cleanupResult = storyClaiming.cleanupStaleClaims({ rootDir });
1019
+ if (cleanupResult.ok && cleanupResult.cleaned > 0) {
1020
+ console.log('');
1021
+ console.log(
1022
+ `${c.dim}Cleaned ${cleanupResult.cleaned} stale story claim(s)${c.reset}`
1023
+ );
1024
+ }
1025
+
1026
+ // Show stories claimed by other sessions
1027
+ const othersResult = storyClaiming.getStoriesClaimedByOthers({ rootDir });
1028
+ if (othersResult.ok && othersResult.stories && othersResult.stories.length > 0) {
1029
+ console.log('');
1030
+ console.log(storyClaiming.formatClaimedStories(othersResult.stories));
1031
+ console.log('');
1032
+ console.log(
1033
+ `${c.slate} These stories are locked - pick a different one to avoid conflicts.${c.reset}`
1034
+ );
1035
+ }
1036
+ } catch (e) {
1037
+ // Silently ignore story claiming errors
1038
+ }
1039
+ }
1040
+
1041
+ // File tracking: cleanup stale touches and show overlap warnings
1042
+ if (fileTracking) {
1043
+ try {
1044
+ // Clean up stale file touches (dead PIDs, expired TTL)
1045
+ const cleanupResult = fileTracking.cleanupStaleTouches({ rootDir });
1046
+ if (cleanupResult.ok && cleanupResult.cleaned > 0) {
1047
+ console.log('');
1048
+ console.log(
1049
+ `${c.dim}Cleaned ${cleanupResult.cleaned} stale file tracking session(s)${c.reset}`
1050
+ );
1051
+ }
1052
+
1053
+ // Show file overlaps with other sessions
1054
+ const overlapsResult = fileTracking.getMyFileOverlaps({ rootDir });
1055
+ if (overlapsResult.ok && overlapsResult.overlaps && overlapsResult.overlaps.length > 0) {
1056
+ console.log('');
1057
+ console.log(fileTracking.formatFileOverlaps(overlapsResult.overlaps));
1058
+ }
1059
+ } catch (e) {
1060
+ // Silently ignore file tracking errors
1061
+ }
1062
+ }
979
1063
  }
980
1064
 
981
1065
  main().catch(console.error);
@@ -1,20 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * check-update.js - Check for AgileFlow updates with caching
4
+ * check-update.js - Check for AgileFlow updates
5
5
  *
6
- * Features:
7
- * - Checks npm registry for latest version
8
- * - Caches results to avoid excessive requests
9
- * - Configurable check frequency (hourly, daily, weekly)
10
- * - Returns structured JSON for easy parsing
6
+ * Simple update checker:
7
+ * - Always checks npm registry for latest version
8
+ * - Auto-updates if update is available (enabled by default)
9
+ * - Fast npm metadata lookup (~100-500ms)
11
10
  *
12
11
  * Usage:
13
- * node check-update.js [--force] [--json]
14
- *
15
- * Options:
16
- * --force Bypass cache and check npm directly
17
- * --json Output as JSON (default is human-readable)
12
+ * node check-update.js [--json]
18
13
  *
19
14
  * Environment:
20
15
  * DEBUG_UPDATE=1 Enable debug logging
@@ -66,12 +61,9 @@ function getInstalledVersion(rootDir) {
66
61
  // Get update configuration from metadata
67
62
  function getUpdateConfig(rootDir) {
68
63
  const defaults = {
69
- autoUpdate: false,
70
- checkFrequency: 'daily', // hourly, daily, weekly, never
64
+ autoUpdate: true, // Auto-update enabled by default
71
65
  showChangelog: true,
72
- lastCheck: null,
73
66
  lastSeenVersion: null,
74
- latestVersion: null,
75
67
  };
76
68
 
77
69
  const metadataPath = path.join(rootDir, 'docs/00-meta/agileflow-metadata.json');
@@ -109,34 +101,6 @@ function saveUpdateConfig(rootDir, config) {
109
101
  return true;
110
102
  }
111
103
 
112
- // Check if cache is still valid
113
- function isCacheValid(config) {
114
- if (!config.lastCheck) return false;
115
-
116
- const now = Date.now();
117
- const lastCheck = new Date(config.lastCheck).getTime();
118
- const age = now - lastCheck;
119
-
120
- // Convert frequency to milliseconds
121
- const frequencies = {
122
- hourly: 60 * 60 * 1000, // 1 hour
123
- daily: 24 * 60 * 60 * 1000, // 24 hours
124
- weekly: 7 * 24 * 60 * 60 * 1000, // 7 days
125
- never: Infinity,
126
- };
127
-
128
- const maxAge = frequencies[config.checkFrequency] || frequencies.daily;
129
-
130
- debugLog('Cache check', {
131
- lastCheck: config.lastCheck,
132
- ageMs: age,
133
- maxAgeMs: maxAge,
134
- valid: age < maxAge,
135
- });
136
-
137
- return age < maxAge;
138
- }
139
-
140
104
  // Fetch latest version from npm
141
105
  async function fetchLatestVersion() {
142
106
  return new Promise(resolve => {
@@ -205,8 +169,8 @@ function compareVersions(a, b) {
205
169
  return 0;
206
170
  }
207
171
 
208
- // Main check function
209
- async function checkForUpdates(options = {}) {
172
+ // Main check function - always checks npm
173
+ async function checkForUpdates() {
210
174
  const rootDir = getProjectRoot();
211
175
  const installedVersion = getInstalledVersion(rootDir);
212
176
  const config = getUpdateConfig(rootDir);
@@ -218,7 +182,6 @@ async function checkForUpdates(options = {}) {
218
182
  autoUpdate: config.autoUpdate,
219
183
  justUpdated: false,
220
184
  previousVersion: config.lastSeenVersion,
221
- fromCache: false,
222
185
  error: null,
223
186
  };
224
187
 
@@ -234,21 +197,8 @@ async function checkForUpdates(options = {}) {
234
197
  result.previousVersion = config.lastSeenVersion;
235
198
  }
236
199
 
237
- // Use cache if valid and not forced
238
- if (!options.force && isCacheValid(config) && config.latestVersion) {
239
- result.latest = config.latestVersion;
240
- result.fromCache = true;
241
- } else if (config.checkFrequency !== 'never') {
242
- // Fetch from npm
243
- result.latest = await fetchLatestVersion();
244
-
245
- // Update cache
246
- if (result.latest) {
247
- config.lastCheck = new Date().toISOString();
248
- config.latestVersion = result.latest;
249
- saveUpdateConfig(rootDir, config);
250
- }
251
- }
200
+ // Always fetch from npm
201
+ result.latest = await fetchLatestVersion();
252
202
 
253
203
  // Compare versions
254
204
  if (result.latest && compareVersions(installedVersion, result.latest) < 0) {
@@ -269,7 +219,6 @@ function markVersionSeen(version) {
269
219
  // CLI interface
270
220
  async function main() {
271
221
  const args = process.argv.slice(2);
272
- const force = args.includes('--force');
273
222
  const jsonOutput = args.includes('--json');
274
223
  const markSeen = args.includes('--mark-seen');
275
224
 
@@ -286,7 +235,7 @@ async function main() {
286
235
  return;
287
236
  }
288
237
 
289
- const result = await checkForUpdates({ force });
238
+ const result = await checkForUpdates();
290
239
 
291
240
  if (jsonOutput) {
292
241
  console.log(JSON.stringify(result, null, 2));