agileflow 2.78.0 → 2.80.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/README.md CHANGED
@@ -3,8 +3,8 @@
3
3
  </p>
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/agileflow?color=brightgreen)](https://www.npmjs.com/package/agileflow)
6
- [![Commands](https://img.shields.io/badge/commands-68-blue)](docs/04-architecture/commands.md)
7
- [![Agents/Experts](https://img.shields.io/badge/agents%2Fexperts-27-orange)](docs/04-architecture/subagents.md)
6
+ [![Commands](https://img.shields.io/badge/commands-69-blue)](docs/04-architecture/commands.md)
7
+ [![Agents/Experts](https://img.shields.io/badge/agents%2Fexperts-28-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.
@@ -65,8 +65,8 @@ AgileFlow combines three proven methodologies:
65
65
 
66
66
  | Component | Count | Description |
67
67
  |-----------|-------|-------------|
68
- | [Commands](docs/04-architecture/commands.md) | 68 | Slash commands for agile workflows |
69
- | [Agents/Experts](docs/04-architecture/subagents.md) | 27 | Specialized agents with self-improving knowledge bases |
68
+ | [Commands](docs/04-architecture/commands.md) | 69 | Slash commands for agile workflows |
69
+ | [Agents/Experts](docs/04-architecture/subagents.md) | 28 | 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
  ---
@@ -76,8 +76,8 @@ AgileFlow combines three proven methodologies:
76
76
  Full documentation lives in [`docs/04-architecture/`](docs/04-architecture/):
77
77
 
78
78
  ### Reference
79
- - [Commands](docs/04-architecture/commands.md) - All 68 slash commands
80
- - [Agents/Experts](docs/04-architecture/subagents.md) - 27 specialized agents with self-improving knowledge
79
+ - [Commands](docs/04-architecture/commands.md) - All 69 slash commands
80
+ - [Agents/Experts](docs/04-architecture/subagents.md) - 28 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agileflow",
3
- "version": "2.78.0",
3
+ "version": "2.80.0",
4
4
  "description": "AI-driven agile development system for Claude Code, Cursor, Windsurf, and more",
5
5
  "keywords": [
6
6
  "agile",
@@ -23,7 +23,7 @@
23
23
  * --detect Show current status
24
24
  * --help Show help
25
25
  *
26
- * Features: sessionstart, precompact, ralphloop, selfimprove, archival, statusline, autoupdate, damagecontrol
26
+ * Features: sessionstart, precompact, ralphloop, selfimprove, archival, statusline, autoupdate, damagecontrol, askuserquestion
27
27
  */
28
28
 
29
29
  const fs = require('fs');
@@ -81,6 +81,7 @@ const FEATURES = {
81
81
  scripts: ['damage-control-bash.js', 'damage-control-edit.js', 'damage-control-write.js'],
82
82
  patternsFile: 'damage-control-patterns.yaml',
83
83
  },
84
+ askuserquestion: { metadataOnly: true }, // Stored in metadata.features.askUserQuestion
84
85
  };
85
86
 
86
87
  // Complete registry of all scripts that may need repair
@@ -131,24 +132,40 @@ const STATUSLINE_COMPONENTS = [
131
132
  const PROFILES = {
132
133
  full: {
133
134
  description: 'All features enabled (including experimental Stop hooks)',
134
- enable: ['sessionstart', 'precompact', 'archival', 'statusline', 'ralphloop', 'selfimprove'],
135
+ enable: [
136
+ 'sessionstart',
137
+ 'precompact',
138
+ 'archival',
139
+ 'statusline',
140
+ 'ralphloop',
141
+ 'selfimprove',
142
+ 'askuserquestion',
143
+ ],
135
144
  archivalDays: 30,
136
145
  },
137
146
  basic: {
138
147
  description: 'Essential hooks + archival (SessionStart + PreCompact + Archival)',
139
- enable: ['sessionstart', 'precompact', 'archival'],
148
+ enable: ['sessionstart', 'precompact', 'archival', 'askuserquestion'],
140
149
  disable: ['statusline', 'ralphloop', 'selfimprove'],
141
150
  archivalDays: 30,
142
151
  },
143
152
  minimal: {
144
153
  description: 'SessionStart + archival only',
145
154
  enable: ['sessionstart', 'archival'],
146
- disable: ['precompact', 'statusline', 'ralphloop', 'selfimprove'],
155
+ disable: ['precompact', 'statusline', 'ralphloop', 'selfimprove', 'askuserquestion'],
147
156
  archivalDays: 30,
148
157
  },
149
158
  none: {
150
159
  description: 'Disable all AgileFlow features',
151
- disable: ['sessionstart', 'precompact', 'archival', 'statusline', 'ralphloop', 'selfimprove'],
160
+ disable: [
161
+ 'sessionstart',
162
+ 'precompact',
163
+ 'archival',
164
+ 'statusline',
165
+ 'ralphloop',
166
+ 'selfimprove',
167
+ 'askuserquestion',
168
+ ],
152
169
  },
153
170
  };
154
171
 
@@ -223,7 +240,23 @@ function detectConfig() {
223
240
  selfimprove: { enabled: false, valid: true, issues: [], version: null, outdated: false },
224
241
  archival: { enabled: false, threshold: null, version: null, outdated: false },
225
242
  statusline: { enabled: false, valid: true, issues: [], version: null, outdated: false },
226
- damagecontrol: { enabled: false, valid: true, issues: [], version: null, outdated: false, level: null, patternCount: 0 },
243
+ damagecontrol: {
244
+ enabled: false,
245
+ valid: true,
246
+ issues: [],
247
+ version: null,
248
+ outdated: false,
249
+ level: null,
250
+ patternCount: 0,
251
+ },
252
+ askuserquestion: {
253
+ enabled: false,
254
+ valid: true,
255
+ issues: [],
256
+ version: null,
257
+ outdated: false,
258
+ mode: null,
259
+ },
227
260
  },
228
261
  metadata: { exists: false, version: null },
229
262
  currentVersion: VERSION,
@@ -313,13 +346,16 @@ function detectConfig() {
313
346
  if (Array.isArray(settings.hooks.PreToolUse) && settings.hooks.PreToolUse.length > 0) {
314
347
  // Check for damage-control hooks by looking for damage-control scripts
315
348
  const hasBashHook = settings.hooks.PreToolUse.some(
316
- h => h.matcher === 'Bash' && h.hooks?.some(hk => hk.command?.includes('damage-control'))
349
+ h =>
350
+ h.matcher === 'Bash' && h.hooks?.some(hk => hk.command?.includes('damage-control'))
317
351
  );
318
352
  const hasEditHook = settings.hooks.PreToolUse.some(
319
- h => h.matcher === 'Edit' && h.hooks?.some(hk => hk.command?.includes('damage-control'))
353
+ h =>
354
+ h.matcher === 'Edit' && h.hooks?.some(hk => hk.command?.includes('damage-control'))
320
355
  );
321
356
  const hasWriteHook = settings.hooks.PreToolUse.some(
322
- h => h.matcher === 'Write' && h.hooks?.some(hk => hk.command?.includes('damage-control'))
357
+ h =>
358
+ h.matcher === 'Write' && h.hooks?.some(hk => hk.command?.includes('damage-control'))
323
359
  );
324
360
 
325
361
  if (hasBashHook || hasEditHook || hasWriteHook) {
@@ -363,17 +399,30 @@ function detectConfig() {
363
399
 
364
400
  // Damage control metadata
365
401
  if (meta.features?.damagecontrol?.enabled) {
366
- status.features.damagecontrol.level = meta.features.damagecontrol.protectionLevel || 'standard';
402
+ status.features.damagecontrol.level =
403
+ meta.features.damagecontrol.protectionLevel || 'standard';
404
+ }
405
+
406
+ // AskUserQuestion metadata
407
+ if (meta.features?.askUserQuestion?.enabled) {
408
+ status.features.askuserquestion.enabled = true;
409
+ status.features.askuserquestion.mode = meta.features.askUserQuestion.mode || 'all';
367
410
  }
368
411
 
369
412
  // Read feature versions from metadata and check if outdated
370
413
  if (meta.features) {
414
+ // Map metadata keys to status keys (handle camelCase differences)
415
+ const featureKeyMap = {
416
+ askUserQuestion: 'askuserquestion',
417
+ };
371
418
  Object.entries(meta.features).forEach(([feature, data]) => {
372
- if (status.features[feature] && data.version) {
373
- status.features[feature].version = data.version;
419
+ // Use mapped key if exists, otherwise lowercase
420
+ const statusKey = featureKeyMap[feature] || feature.toLowerCase();
421
+ if (status.features[statusKey] && data.version) {
422
+ status.features[statusKey].version = data.version;
374
423
  // Check if feature version differs from current VERSION
375
- if (data.version !== VERSION && status.features[feature].enabled) {
376
- status.features[feature].outdated = true;
424
+ if (data.version !== VERSION && status.features[statusKey].enabled) {
425
+ status.features[statusKey].outdated = true;
377
426
  status.hasOutdated = true;
378
427
  }
379
428
  }
@@ -458,6 +507,16 @@ function printStatus(status) {
458
507
  log(` ❌ Damage Control: disabled`, c.dim);
459
508
  }
460
509
 
510
+ // AskUserQuestion
511
+ const auq = status.features.askuserquestion;
512
+ if (auq.enabled) {
513
+ let auqStatusText = 'enabled';
514
+ if (auq.mode) auqStatusText += ` (mode: ${auq.mode})`;
515
+ log(` 💬 AskUserQuestion: ${auqStatusText}`, c.green);
516
+ } else {
517
+ log(` ❌ AskUserQuestion: disabled`, c.dim);
518
+ }
519
+
461
520
  // Metadata
462
521
  if (status.metadata.exists) {
463
522
  log(`\nMetadata: v${status.metadata.version}`, c.dim);
@@ -741,12 +800,34 @@ function enableFeature(feature, options = {}) {
741
800
  return true; // Skip settings.json write for this feature
742
801
  }
743
802
 
803
+ // Handle askuserquestion (metadata only, no hooks needed)
804
+ if (feature === 'askuserquestion') {
805
+ const mode = options.mode || 'all';
806
+ updateMetadata({
807
+ features: {
808
+ askUserQuestion: {
809
+ enabled: true,
810
+ mode: mode,
811
+ version: VERSION,
812
+ at: new Date().toISOString(),
813
+ },
814
+ },
815
+ });
816
+ success(`AskUserQuestion enabled (mode: ${mode})`);
817
+ info('All commands will end with AskUserQuestion tool for guided interaction');
818
+ return true; // Skip settings.json write for this feature
819
+ }
820
+
744
821
  // Handle damage control (PreToolUse hooks)
745
822
  if (feature === 'damagecontrol') {
746
823
  const level = options.protectionLevel || 'standard';
747
824
 
748
825
  // Verify all required scripts exist
749
- const requiredScripts = ['damage-control-bash.js', 'damage-control-edit.js', 'damage-control-write.js'];
826
+ const requiredScripts = [
827
+ 'damage-control-bash.js',
828
+ 'damage-control-edit.js',
829
+ 'damage-control-write.js',
830
+ ];
750
831
  for (const script of requiredScripts) {
751
832
  if (!scriptExists(script)) {
752
833
  error(`Script not found: ${getScriptPath(script)}`);
@@ -761,7 +842,12 @@ function enableFeature(feature, options = {}) {
761
842
  if (!fs.existsSync(patternsDest)) {
762
843
  ensureDir(patternsDir);
763
844
  // Try to copy from templates
764
- const templatePath = path.join(process.cwd(), '.agileflow', 'templates', 'damage-control-patterns.yaml');
845
+ const templatePath = path.join(
846
+ process.cwd(),
847
+ '.agileflow',
848
+ 'templates',
849
+ 'damage-control-patterns.yaml'
850
+ );
765
851
  if (fs.existsSync(templatePath)) {
766
852
  fs.copyFileSync(templatePath, patternsDest);
767
853
  success('Deployed damage control patterns');
@@ -893,6 +979,22 @@ function disableFeature(feature) {
893
979
  return true; // Skip settings.json write for this feature
894
980
  }
895
981
 
982
+ // Disable askuserquestion
983
+ if (feature === 'askuserquestion') {
984
+ updateMetadata({
985
+ features: {
986
+ askUserQuestion: {
987
+ enabled: false,
988
+ version: VERSION,
989
+ at: new Date().toISOString(),
990
+ },
991
+ },
992
+ });
993
+ success('AskUserQuestion disabled');
994
+ info('Commands will end with natural text questions instead of AskUserQuestion tool');
995
+ return true; // Skip settings.json write for this feature
996
+ }
997
+
896
998
  // Disable damage control (PreToolUse hooks)
897
999
  if (feature === 'damagecontrol') {
898
1000
  if (settings.hooks?.PreToolUse && Array.isArray(settings.hooks.PreToolUse)) {
@@ -1396,10 +1498,11 @@ ${c.cyan}Feature Control:${c.reset}
1396
1498
  --enable=<list> Enable features (comma-separated)
1397
1499
  --disable=<list> Disable features (comma-separated)
1398
1500
 
1399
- Features: sessionstart, precompact, ralphloop, selfimprove, archival, statusline, damagecontrol
1501
+ Features: sessionstart, precompact, ralphloop, selfimprove, archival, statusline, damagecontrol, askuserquestion
1400
1502
 
1401
1503
  Stop hooks (ralphloop, selfimprove) run when Claude completes/pauses
1402
1504
  Damage control (damagecontrol) uses PreToolUse hooks to block dangerous commands
1505
+ AskUserQuestion (askuserquestion) makes all commands end with guided options
1403
1506
 
1404
1507
  ${c.cyan}Statusline Components:${c.reset}
1405
1508
  --show=<list> Show statusline components (comma-separated)
@@ -1465,6 +1568,12 @@ ${c.cyan}Examples:${c.reset}
1465
1568
 
1466
1569
  # Enable damage control (PreToolUse hooks to block dangerous commands)
1467
1570
  node .agileflow/scripts/agileflow-configure.js --enable=damagecontrol
1571
+
1572
+ # Enable AskUserQuestion (all commands end with guided options)
1573
+ node .agileflow/scripts/agileflow-configure.js --enable=askuserquestion
1574
+
1575
+ # Disable AskUserQuestion (commands end with natural text questions)
1576
+ node .agileflow/scripts/agileflow-configure.js --disable=askuserquestion
1468
1577
  `);
1469
1578
  }
1470
1579
 
@@ -15,6 +15,10 @@ const fs = require('fs');
15
15
  const path = require('path');
16
16
  const { execSync, spawnSync } = require('child_process');
17
17
 
18
+ // Shared utilities
19
+ const { c, box } = require('../lib/colors');
20
+ const { getProjectRoot } = require('../lib/paths');
21
+
18
22
  // Session manager path (relative to script location)
19
23
  const SESSION_MANAGER_PATH = path.join(__dirname, 'session-manager.js');
20
24
 
@@ -26,68 +30,6 @@ try {
26
30
  // Update checker not available
27
31
  }
28
32
 
29
- // ANSI color codes
30
- const c = {
31
- reset: '\x1b[0m',
32
- bold: '\x1b[1m',
33
- dim: '\x1b[2m',
34
-
35
- // Standard ANSI colors
36
- red: '\x1b[31m',
37
- green: '\x1b[32m',
38
- yellow: '\x1b[33m',
39
- blue: '\x1b[34m',
40
- magenta: '\x1b[35m',
41
- cyan: '\x1b[36m',
42
-
43
- brightBlack: '\x1b[90m',
44
- brightGreen: '\x1b[92m',
45
- brightYellow: '\x1b[93m',
46
- brightCyan: '\x1b[96m',
47
-
48
- // Vibrant 256-color palette (modern, sleek look)
49
- mintGreen: '\x1b[38;5;158m', // Healthy/success states
50
- peach: '\x1b[38;5;215m', // Warning states
51
- coral: '\x1b[38;5;203m', // Critical/error states
52
- lightGreen: '\x1b[38;5;194m', // Session healthy
53
- lightYellow: '\x1b[38;5;228m', // Session warning
54
- lightPink: '\x1b[38;5;210m', // Session critical
55
- skyBlue: '\x1b[38;5;117m', // Directories/paths
56
- lavender: '\x1b[38;5;147m', // Model info, story IDs
57
- softGold: '\x1b[38;5;222m', // Cost/money
58
- teal: '\x1b[38;5;80m', // Ready/pending states
59
- slate: '\x1b[38;5;103m', // Secondary info
60
- rose: '\x1b[38;5;211m', // Blocked/critical accent
61
- amber: '\x1b[38;5;214m', // WIP/in-progress accent
62
- powder: '\x1b[38;5;153m', // Labels/headers
63
-
64
- // Brand color (#e8683a)
65
- brand: '\x1b[38;2;232;104;58m',
66
- };
67
-
68
- // Box drawing characters
69
- const box = {
70
- tl: '╭',
71
- tr: '╮',
72
- bl: '╰',
73
- br: '╯',
74
- h: '─',
75
- v: '│',
76
- lT: '├',
77
- rT: '┤',
78
- tT: '┬',
79
- bT: '┴',
80
- cross: '┼',
81
- };
82
-
83
- function getProjectRoot() {
84
- let dir = process.cwd();
85
- while (!fs.existsSync(path.join(dir, '.agileflow')) && dir !== '/') {
86
- dir = path.dirname(dir);
87
- }
88
- return dir !== '/' ? dir : process.cwd();
89
- }
90
-
91
33
  function getProjectInfo(rootDir) {
92
34
  const info = {
93
35
  name: 'agileflow',
@@ -229,6 +171,7 @@ function clearActiveCommands(rootDir) {
229
171
  const state = JSON.parse(fs.readFileSync(sessionStatePath, 'utf8'));
230
172
  result.ran = true;
231
173
 
174
+ // Handle new array format (active_commands)
232
175
  if (state.active_commands && state.active_commands.length > 0) {
233
176
  result.cleared = state.active_commands.length;
234
177
  // Capture command names before clearing
@@ -237,11 +180,14 @@ function clearActiveCommands(rootDir) {
237
180
  }
238
181
  state.active_commands = [];
239
182
  }
183
+
184
+ // Handle legacy singular format (active_command) - only capture if not already in array
240
185
  if (state.active_command !== undefined) {
241
- result.cleared++;
242
- // Capture single command name
243
- if (state.active_command.name) {
244
- result.commandNames.push(state.active_command.name);
186
+ const legacyName = state.active_command.name;
187
+ // Only add to count/names if not already captured from array (avoid duplicates)
188
+ if (legacyName && !result.commandNames.includes(legacyName)) {
189
+ result.cleared++;
190
+ result.commandNames.push(legacyName);
245
191
  }
246
192
  delete state.active_command;
247
193
  }
@@ -347,7 +293,7 @@ function checkPreCompact(rootDir) {
347
293
  }
348
294
 
349
295
  function checkDamageControl(rootDir) {
350
- const result = { configured: false, level: null, patternCount: 0, scriptsOk: true };
296
+ const result = { configured: false, level: 'standard', patternCount: 0, scriptsOk: true };
351
297
 
352
298
  try {
353
299
  // Check if PreToolUse hooks are configured in settings
@@ -356,8 +302,8 @@ function checkDamageControl(rootDir) {
356
302
  const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
357
303
  if (settings.hooks?.PreToolUse && Array.isArray(settings.hooks.PreToolUse)) {
358
304
  // Check for damage-control hooks
359
- const hasDamageControlHooks = settings.hooks.PreToolUse.some(
360
- h => h.hooks?.some(hk => hk.command?.includes('damage-control'))
305
+ const hasDamageControlHooks = settings.hooks.PreToolUse.some(h =>
306
+ h.hooks?.some(hk => hk.command?.includes('damage-control'))
361
307
  );
362
308
  if (hasDamageControlHooks) {
363
309
  result.configured = true;
@@ -368,15 +314,23 @@ function checkDamageControl(rootDir) {
368
314
  );
369
315
  result.hooksCount = dcHooks.length;
370
316
 
371
- // Check if all required scripts exist
372
- const scriptsDir = path.join(rootDir, '.agileflow', 'scripts');
317
+ // Check for enhanced mode (has prompt hook)
318
+ const hasPromptHook = settings.hooks.PreToolUse.some(h =>
319
+ h.hooks?.some(hk => hk.type === 'prompt')
320
+ );
321
+ if (hasPromptHook) {
322
+ result.level = 'enhanced';
323
+ }
324
+
325
+ // Check if all required scripts exist (in .claude/hooks/damage-control/)
326
+ const hooksDir = path.join(rootDir, '.claude', 'hooks', 'damage-control');
373
327
  const requiredScripts = [
374
- 'damage-control-bash.js',
375
- 'damage-control-edit.js',
376
- 'damage-control-write.js',
328
+ 'bash-tool-damage-control.js',
329
+ 'edit-tool-damage-control.js',
330
+ 'write-tool-damage-control.js',
377
331
  ];
378
332
  for (const script of requiredScripts) {
379
- if (!fs.existsSync(path.join(scriptsDir, script))) {
333
+ if (!fs.existsSync(path.join(hooksDir, script))) {
380
334
  result.scriptsOk = false;
381
335
  break;
382
336
  }
@@ -385,23 +339,20 @@ function checkDamageControl(rootDir) {
385
339
  }
386
340
  }
387
341
 
388
- // Get protection level and pattern count from metadata
389
- const metadataPath = path.join(rootDir, 'docs/00-meta/agileflow-metadata.json');
390
- if (fs.existsSync(metadataPath)) {
391
- const metadata = JSON.parse(fs.readFileSync(metadataPath, 'utf8'));
392
- if (metadata.features?.damagecontrol) {
393
- result.level = metadata.features.damagecontrol.protectionLevel || 'standard';
342
+ // Count patterns in patterns.yaml
343
+ const patternsLocations = [
344
+ path.join(rootDir, '.claude', 'hooks', 'damage-control', 'patterns.yaml'),
345
+ path.join(rootDir, '.agileflow', 'scripts', 'damage-control', 'patterns.yaml'),
346
+ ];
347
+ for (const patternsPath of patternsLocations) {
348
+ if (fs.existsSync(patternsPath)) {
349
+ const content = fs.readFileSync(patternsPath, 'utf8');
350
+ // Count pattern entries (lines starting with " - pattern:")
351
+ const patternMatches = content.match(/^\s*-\s*pattern:/gm);
352
+ result.patternCount = patternMatches ? patternMatches.length : 0;
353
+ break;
394
354
  }
395
355
  }
396
-
397
- // Count patterns in config file
398
- const patternsPath = path.join(rootDir, '.agileflow', 'config', 'damage-control-patterns.yaml');
399
- if (fs.existsSync(patternsPath)) {
400
- const content = fs.readFileSync(patternsPath, 'utf8');
401
- // Count pattern entries (lines starting with " - pattern:")
402
- const patternMatches = content.match(/^\s*-\s*pattern:/gm);
403
- result.patternCount = patternMatches ? patternMatches.length : 0;
404
- }
405
356
  } catch (e) {}
406
357
 
407
358
  return result;
@@ -733,14 +684,24 @@ function formatTable(
733
684
  // Show update available notification (using vibrant colors)
734
685
  if (updateInfo.available && updateInfo.latest && !updateInfo.justUpdated) {
735
686
  lines.push(fullDivider());
736
- lines.push(fullRow(`${c.amber}↑${c.reset} Update available: ${c.softGold}v${updateInfo.latest}${c.reset}`, ''));
687
+ lines.push(
688
+ fullRow(
689
+ `${c.amber}↑${c.reset} Update available: ${c.softGold}v${updateInfo.latest}${c.reset}`,
690
+ ''
691
+ )
692
+ );
737
693
  lines.push(fullRow(` Run: ${c.skyBlue}npx agileflow update${c.reset}`, ''));
738
694
  }
739
695
 
740
696
  // Show "just updated" changelog
741
697
  if (updateInfo.justUpdated && updateInfo.changelog && updateInfo.changelog.length > 0) {
742
698
  lines.push(fullDivider());
743
- lines.push(fullRow(`${c.mintGreen}✨${c.reset} What's new in ${c.softGold}v${info.version}${c.reset}:`, ''));
699
+ lines.push(
700
+ fullRow(
701
+ `${c.mintGreen}✨${c.reset} What's new in ${c.softGold}v${info.version}${c.reset}:`,
702
+ ''
703
+ )
704
+ );
744
705
  for (const entry of updateInfo.changelog.slice(0, 2)) {
745
706
  lines.push(fullRow(` ${c.teal}•${c.reset} ${truncate(entry, W - 6)}`, ''));
746
707
  }
@@ -799,7 +760,9 @@ function formatTable(
799
760
 
800
761
  // Session cleanup
801
762
  const sessionStatus = session.cleared > 0 ? `cleared ${session.cleared} command(s)` : `clean`;
802
- lines.push(row('Session state', sessionStatus, c.lavender, session.cleared > 0 ? c.mintGreen : c.dim));
763
+ lines.push(
764
+ row('Session state', sessionStatus, c.lavender, session.cleared > 0 ? c.mintGreen : c.dim)
765
+ );
803
766
 
804
767
  // PreCompact status with version check
805
768
  if (precompact.configured && precompact.scriptExists) {
@@ -851,7 +814,8 @@ function formatTable(
851
814
  lines.push(row('Damage control', '⚠️ scripts missing', c.coral, c.coral));
852
815
  } else {
853
816
  const levelStr = damageControl.level || 'standard';
854
- const patternStr = damageControl.patternCount > 0 ? `${damageControl.patternCount} patterns` : '';
817
+ const patternStr =
818
+ damageControl.patternCount > 0 ? `${damageControl.patternCount} patterns` : '';
855
819
  const dcStatus = `🛡️ ${levelStr}${patternStr ? ` (${patternStr})` : ''}`;
856
820
  lines.push(row('Damage control', dcStatus, c.lavender, c.mintGreen));
857
821
  }
@@ -876,7 +840,9 @@ function formatTable(
876
840
  }
877
841
 
878
842
  // Last commit (colorful like obtain-context)
879
- lines.push(row('Last commit', `${c.peach}${info.commit}${c.reset} ${info.lastCommit}`, c.lavender, ''));
843
+ lines.push(
844
+ row('Last commit', `${c.peach}${info.commit}${c.reset} ${info.lastCommit}`, c.lavender, '')
845
+ );
880
846
 
881
847
  lines.push(bottomBorder);
882
848
 
@@ -917,15 +883,28 @@ async function main() {
917
883
  }
918
884
 
919
885
  console.log(
920
- formatTable(info, archival, session, precompact, parallelSessions, updateInfo, expertise, damageControl)
886
+ formatTable(
887
+ info,
888
+ archival,
889
+ session,
890
+ precompact,
891
+ parallelSessions,
892
+ updateInfo,
893
+ expertise,
894
+ damageControl
895
+ )
921
896
  );
922
897
 
923
898
  // Show warning and tip if other sessions are active (vibrant colors)
924
899
  if (parallelSessions.otherActive > 0) {
925
900
  console.log('');
926
901
  console.log(`${c.amber}⚠️ Other Claude session(s) active in this repo.${c.reset}`);
927
- console.log(`${c.slate} Run ${c.skyBlue}/agileflow:session:status${c.reset}${c.slate} to see all sessions.${c.reset}`);
928
- console.log(`${c.slate} Run ${c.skyBlue}/agileflow:session:new${c.reset}${c.slate} to create isolated workspace.${c.reset}`);
902
+ console.log(
903
+ `${c.slate} Run ${c.skyBlue}/agileflow:session:status${c.reset}${c.slate} to see all sessions.${c.reset}`
904
+ );
905
+ console.log(
906
+ `${c.slate} Run ${c.skyBlue}/agileflow:session:new${c.reset}${c.slate} to create isolated workspace.${c.reset}`
907
+ );
929
908
  }
930
909
  }
931
910
 
@@ -21,18 +21,10 @@ const fs = require('fs');
21
21
  const path = require('path');
22
22
  const { execSync } = require('child_process');
23
23
 
24
- // ANSI colors
25
- const c = {
26
- reset: '\x1b[0m',
27
- bold: '\x1b[1m',
28
- dim: '\x1b[2m',
29
- red: '\x1b[31m',
30
- green: '\x1b[32m',
31
- yellow: '\x1b[33m',
32
- blue: '\x1b[34m',
33
- cyan: '\x1b[36m',
34
- brand: '\x1b[38;2;232;104;58m',
35
- };
24
+ // Shared utilities
25
+ const { c } = require('../lib/colors');
26
+ const { getProjectRoot } = require('../lib/paths');
27
+ const { safeReadJSON, safeReadFile, safeWriteFile } = require('../lib/errors');
36
28
 
37
29
  // Agents that have expertise files
38
30
  const AGENTS_WITH_EXPERTISE = [
@@ -76,24 +68,11 @@ const DOMAIN_PATTERNS = {
76
68
  devops: [/deploy/, /kubernetes/, /k8s/, /terraform/, /ansible/],
77
69
  };
78
70
 
79
- // Find project root
80
- function getProjectRoot() {
81
- let dir = process.cwd();
82
- while (!fs.existsSync(path.join(dir, '.agileflow')) && dir !== '/') {
83
- dir = path.dirname(dir);
84
- }
85
- return dir !== '/' ? dir : process.cwd();
86
- }
87
-
88
71
  // Read session state
89
72
  function getSessionState(rootDir) {
90
73
  const statePath = path.join(rootDir, 'docs/09-agents/session-state.json');
91
- try {
92
- if (fs.existsSync(statePath)) {
93
- return JSON.parse(fs.readFileSync(statePath, 'utf8'));
94
- }
95
- } catch (e) {}
96
- return {};
74
+ const result = safeReadJSON(statePath, { defaultValue: {} });
75
+ return result.ok ? result.data : {};
97
76
  }
98
77
 
99
78
  // Get git diff summary
@@ -232,26 +211,25 @@ function getExpertisePath(rootDir, agent) {
232
211
 
233
212
  // Append learning to expertise file
234
213
  function appendLearning(expertisePath, learning) {
235
- try {
236
- let content = fs.readFileSync(expertisePath, 'utf8');
237
-
238
- // Find the learnings section
239
- const learningsMatch = content.match(/^learnings:\s*$/m);
240
-
241
- if (!learningsMatch) {
242
- // No learnings section, add it at the end
243
- content += `\n\nlearnings:\n${learning}`;
244
- } else {
245
- // Find where to insert (after "learnings:" line)
246
- const insertPos = learningsMatch.index + learningsMatch[0].length;
247
- content = content.slice(0, insertPos) + '\n' + learning + content.slice(insertPos);
248
- }
214
+ const readResult = safeReadFile(expertisePath);
215
+ if (!readResult.ok) return false;
249
216
 
250
- fs.writeFileSync(expertisePath, content);
251
- return true;
252
- } catch (e) {
253
- return false;
217
+ let content = readResult.data;
218
+
219
+ // Find the learnings section
220
+ const learningsMatch = content.match(/^learnings:\s*$/m);
221
+
222
+ if (!learningsMatch) {
223
+ // No learnings section, add it at the end
224
+ content += `\n\nlearnings:\n${learning}`;
225
+ } else {
226
+ // Find where to insert (after "learnings:" line)
227
+ const insertPos = learningsMatch.index + learningsMatch[0].length;
228
+ content = content.slice(0, insertPos) + '\n' + learning + content.slice(insertPos);
254
229
  }
230
+
231
+ const writeResult = safeWriteFile(expertisePath, content);
232
+ return writeResult.ok;
255
233
  }
256
234
 
257
235
  // Format learning as YAML