agileflow 2.79.0 → 2.81.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.
@@ -23,12 +23,13 @@
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');
30
30
  const path = require('path');
31
31
  const { execSync } = require('child_process');
32
+ const { isValidProfileName, isValidFeatureName, parseIntBounded } = require('../lib/validate');
32
33
 
33
34
  // ============================================================================
34
35
  // CONFIGURATION
@@ -81,6 +82,7 @@ const FEATURES = {
81
82
  scripts: ['damage-control-bash.js', 'damage-control-edit.js', 'damage-control-write.js'],
82
83
  patternsFile: 'damage-control-patterns.yaml',
83
84
  },
85
+ askuserquestion: { metadataOnly: true }, // Stored in metadata.features.askUserQuestion
84
86
  };
85
87
 
86
88
  // Complete registry of all scripts that may need repair
@@ -131,24 +133,40 @@ const STATUSLINE_COMPONENTS = [
131
133
  const PROFILES = {
132
134
  full: {
133
135
  description: 'All features enabled (including experimental Stop hooks)',
134
- enable: ['sessionstart', 'precompact', 'archival', 'statusline', 'ralphloop', 'selfimprove'],
136
+ enable: [
137
+ 'sessionstart',
138
+ 'precompact',
139
+ 'archival',
140
+ 'statusline',
141
+ 'ralphloop',
142
+ 'selfimprove',
143
+ 'askuserquestion',
144
+ ],
135
145
  archivalDays: 30,
136
146
  },
137
147
  basic: {
138
148
  description: 'Essential hooks + archival (SessionStart + PreCompact + Archival)',
139
- enable: ['sessionstart', 'precompact', 'archival'],
149
+ enable: ['sessionstart', 'precompact', 'archival', 'askuserquestion'],
140
150
  disable: ['statusline', 'ralphloop', 'selfimprove'],
141
151
  archivalDays: 30,
142
152
  },
143
153
  minimal: {
144
154
  description: 'SessionStart + archival only',
145
155
  enable: ['sessionstart', 'archival'],
146
- disable: ['precompact', 'statusline', 'ralphloop', 'selfimprove'],
156
+ disable: ['precompact', 'statusline', 'ralphloop', 'selfimprove', 'askuserquestion'],
147
157
  archivalDays: 30,
148
158
  },
149
159
  none: {
150
160
  description: 'Disable all AgileFlow features',
151
- disable: ['sessionstart', 'precompact', 'archival', 'statusline', 'ralphloop', 'selfimprove'],
161
+ disable: [
162
+ 'sessionstart',
163
+ 'precompact',
164
+ 'archival',
165
+ 'statusline',
166
+ 'ralphloop',
167
+ 'selfimprove',
168
+ 'askuserquestion',
169
+ ],
152
170
  },
153
171
  };
154
172
 
@@ -223,7 +241,23 @@ function detectConfig() {
223
241
  selfimprove: { enabled: false, valid: true, issues: [], version: null, outdated: false },
224
242
  archival: { enabled: false, threshold: null, version: null, outdated: false },
225
243
  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 },
244
+ damagecontrol: {
245
+ enabled: false,
246
+ valid: true,
247
+ issues: [],
248
+ version: null,
249
+ outdated: false,
250
+ level: null,
251
+ patternCount: 0,
252
+ },
253
+ askuserquestion: {
254
+ enabled: false,
255
+ valid: true,
256
+ issues: [],
257
+ version: null,
258
+ outdated: false,
259
+ mode: null,
260
+ },
227
261
  },
228
262
  metadata: { exists: false, version: null },
229
263
  currentVersion: VERSION,
@@ -313,13 +347,16 @@ function detectConfig() {
313
347
  if (Array.isArray(settings.hooks.PreToolUse) && settings.hooks.PreToolUse.length > 0) {
314
348
  // Check for damage-control hooks by looking for damage-control scripts
315
349
  const hasBashHook = settings.hooks.PreToolUse.some(
316
- h => h.matcher === 'Bash' && h.hooks?.some(hk => hk.command?.includes('damage-control'))
350
+ h =>
351
+ h.matcher === 'Bash' && h.hooks?.some(hk => hk.command?.includes('damage-control'))
317
352
  );
318
353
  const hasEditHook = settings.hooks.PreToolUse.some(
319
- h => h.matcher === 'Edit' && h.hooks?.some(hk => hk.command?.includes('damage-control'))
354
+ h =>
355
+ h.matcher === 'Edit' && h.hooks?.some(hk => hk.command?.includes('damage-control'))
320
356
  );
321
357
  const hasWriteHook = settings.hooks.PreToolUse.some(
322
- h => h.matcher === 'Write' && h.hooks?.some(hk => hk.command?.includes('damage-control'))
358
+ h =>
359
+ h.matcher === 'Write' && h.hooks?.some(hk => hk.command?.includes('damage-control'))
323
360
  );
324
361
 
325
362
  if (hasBashHook || hasEditHook || hasWriteHook) {
@@ -363,17 +400,30 @@ function detectConfig() {
363
400
 
364
401
  // Damage control metadata
365
402
  if (meta.features?.damagecontrol?.enabled) {
366
- status.features.damagecontrol.level = meta.features.damagecontrol.protectionLevel || 'standard';
403
+ status.features.damagecontrol.level =
404
+ meta.features.damagecontrol.protectionLevel || 'standard';
405
+ }
406
+
407
+ // AskUserQuestion metadata
408
+ if (meta.features?.askUserQuestion?.enabled) {
409
+ status.features.askuserquestion.enabled = true;
410
+ status.features.askuserquestion.mode = meta.features.askUserQuestion.mode || 'all';
367
411
  }
368
412
 
369
413
  // Read feature versions from metadata and check if outdated
370
414
  if (meta.features) {
415
+ // Map metadata keys to status keys (handle camelCase differences)
416
+ const featureKeyMap = {
417
+ askUserQuestion: 'askuserquestion',
418
+ };
371
419
  Object.entries(meta.features).forEach(([feature, data]) => {
372
- if (status.features[feature] && data.version) {
373
- status.features[feature].version = data.version;
420
+ // Use mapped key if exists, otherwise lowercase
421
+ const statusKey = featureKeyMap[feature] || feature.toLowerCase();
422
+ if (status.features[statusKey] && data.version) {
423
+ status.features[statusKey].version = data.version;
374
424
  // Check if feature version differs from current VERSION
375
- if (data.version !== VERSION && status.features[feature].enabled) {
376
- status.features[feature].outdated = true;
425
+ if (data.version !== VERSION && status.features[statusKey].enabled) {
426
+ status.features[statusKey].outdated = true;
377
427
  status.hasOutdated = true;
378
428
  }
379
429
  }
@@ -458,6 +508,16 @@ function printStatus(status) {
458
508
  log(` ❌ Damage Control: disabled`, c.dim);
459
509
  }
460
510
 
511
+ // AskUserQuestion
512
+ const auq = status.features.askuserquestion;
513
+ if (auq.enabled) {
514
+ let auqStatusText = 'enabled';
515
+ if (auq.mode) auqStatusText += ` (mode: ${auq.mode})`;
516
+ log(` 💬 AskUserQuestion: ${auqStatusText}`, c.green);
517
+ } else {
518
+ log(` ❌ AskUserQuestion: disabled`, c.dim);
519
+ }
520
+
461
521
  // Metadata
462
522
  if (status.metadata.exists) {
463
523
  log(`\nMetadata: v${status.metadata.version}`, c.dim);
@@ -741,12 +801,34 @@ function enableFeature(feature, options = {}) {
741
801
  return true; // Skip settings.json write for this feature
742
802
  }
743
803
 
804
+ // Handle askuserquestion (metadata only, no hooks needed)
805
+ if (feature === 'askuserquestion') {
806
+ const mode = options.mode || 'all';
807
+ updateMetadata({
808
+ features: {
809
+ askUserQuestion: {
810
+ enabled: true,
811
+ mode: mode,
812
+ version: VERSION,
813
+ at: new Date().toISOString(),
814
+ },
815
+ },
816
+ });
817
+ success(`AskUserQuestion enabled (mode: ${mode})`);
818
+ info('All commands will end with AskUserQuestion tool for guided interaction');
819
+ return true; // Skip settings.json write for this feature
820
+ }
821
+
744
822
  // Handle damage control (PreToolUse hooks)
745
823
  if (feature === 'damagecontrol') {
746
824
  const level = options.protectionLevel || 'standard';
747
825
 
748
826
  // Verify all required scripts exist
749
- const requiredScripts = ['damage-control-bash.js', 'damage-control-edit.js', 'damage-control-write.js'];
827
+ const requiredScripts = [
828
+ 'damage-control-bash.js',
829
+ 'damage-control-edit.js',
830
+ 'damage-control-write.js',
831
+ ];
750
832
  for (const script of requiredScripts) {
751
833
  if (!scriptExists(script)) {
752
834
  error(`Script not found: ${getScriptPath(script)}`);
@@ -761,7 +843,12 @@ function enableFeature(feature, options = {}) {
761
843
  if (!fs.existsSync(patternsDest)) {
762
844
  ensureDir(patternsDir);
763
845
  // Try to copy from templates
764
- const templatePath = path.join(process.cwd(), '.agileflow', 'templates', 'damage-control-patterns.yaml');
846
+ const templatePath = path.join(
847
+ process.cwd(),
848
+ '.agileflow',
849
+ 'templates',
850
+ 'damage-control-patterns.yaml'
851
+ );
765
852
  if (fs.existsSync(templatePath)) {
766
853
  fs.copyFileSync(templatePath, patternsDest);
767
854
  success('Deployed damage control patterns');
@@ -893,6 +980,22 @@ function disableFeature(feature) {
893
980
  return true; // Skip settings.json write for this feature
894
981
  }
895
982
 
983
+ // Disable askuserquestion
984
+ if (feature === 'askuserquestion') {
985
+ updateMetadata({
986
+ features: {
987
+ askUserQuestion: {
988
+ enabled: false,
989
+ version: VERSION,
990
+ at: new Date().toISOString(),
991
+ },
992
+ },
993
+ });
994
+ success('AskUserQuestion disabled');
995
+ info('Commands will end with natural text questions instead of AskUserQuestion tool');
996
+ return true; // Skip settings.json write for this feature
997
+ }
998
+
896
999
  // Disable damage control (PreToolUse hooks)
897
1000
  if (feature === 'damagecontrol') {
898
1001
  if (settings.hooks?.PreToolUse && Array.isArray(settings.hooks.PreToolUse)) {
@@ -1396,10 +1499,11 @@ ${c.cyan}Feature Control:${c.reset}
1396
1499
  --enable=<list> Enable features (comma-separated)
1397
1500
  --disable=<list> Disable features (comma-separated)
1398
1501
 
1399
- Features: sessionstart, precompact, ralphloop, selfimprove, archival, statusline, damagecontrol
1502
+ Features: sessionstart, precompact, ralphloop, selfimprove, archival, statusline, damagecontrol, askuserquestion
1400
1503
 
1401
1504
  Stop hooks (ralphloop, selfimprove) run when Claude completes/pauses
1402
1505
  Damage control (damagecontrol) uses PreToolUse hooks to block dangerous commands
1506
+ AskUserQuestion (askuserquestion) makes all commands end with guided options
1403
1507
 
1404
1508
  ${c.cyan}Statusline Components:${c.reset}
1405
1509
  --show=<list> Show statusline components (comma-separated)
@@ -1465,6 +1569,12 @@ ${c.cyan}Examples:${c.reset}
1465
1569
 
1466
1570
  # Enable damage control (PreToolUse hooks to block dangerous commands)
1467
1571
  node .agileflow/scripts/agileflow-configure.js --enable=damagecontrol
1572
+
1573
+ # Enable AskUserQuestion (all commands end with guided options)
1574
+ node .agileflow/scripts/agileflow-configure.js --enable=askuserquestion
1575
+
1576
+ # Disable AskUserQuestion (commands end with natural text questions)
1577
+ node .agileflow/scripts/agileflow-configure.js --disable=askuserquestion
1468
1578
  `);
1469
1579
  }
1470
1580
 
@@ -1514,7 +1624,8 @@ function main() {
1514
1624
  .split('=')[1]
1515
1625
  .split(',')
1516
1626
  .map(s => s.trim().toLowerCase());
1517
- else if (arg.startsWith('--archival-days=')) archivalDays = parseInt(arg.split('=')[1]) || 30;
1627
+ else if (arg.startsWith('--archival-days='))
1628
+ archivalDays = parseIntBounded(arg.split('=')[1], 30, 1, 365);
1518
1629
  else if (arg === '--migrate') migrate = true;
1519
1630
  else if (arg === '--detect' || arg === '--validate') detect = true;
1520
1631
  else if (arg === '--upgrade') upgrade = true;
@@ -171,6 +171,7 @@ function clearActiveCommands(rootDir) {
171
171
  const state = JSON.parse(fs.readFileSync(sessionStatePath, 'utf8'));
172
172
  result.ran = true;
173
173
 
174
+ // Handle new array format (active_commands)
174
175
  if (state.active_commands && state.active_commands.length > 0) {
175
176
  result.cleared = state.active_commands.length;
176
177
  // Capture command names before clearing
@@ -179,11 +180,14 @@ function clearActiveCommands(rootDir) {
179
180
  }
180
181
  state.active_commands = [];
181
182
  }
183
+
184
+ // Handle legacy singular format (active_command) - only capture if not already in array
182
185
  if (state.active_command !== undefined) {
183
- result.cleared++;
184
- // Capture single command name
185
- if (state.active_command.name) {
186
- 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);
187
191
  }
188
192
  delete state.active_command;
189
193
  }
@@ -203,6 +207,12 @@ function checkParallelSessions(rootDir) {
203
207
  otherActive: 0,
204
208
  currentId: null,
205
209
  cleaned: 0,
210
+ // Extended session info for non-main sessions
211
+ isMain: true,
212
+ nickname: null,
213
+ branch: null,
214
+ sessionPath: null,
215
+ mainPath: rootDir,
206
216
  };
207
217
 
208
218
  try {
@@ -243,6 +253,24 @@ function checkParallelSessions(rootDir) {
243
253
  } catch (e) {
244
254
  // Count failed
245
255
  }
256
+
257
+ // Get detailed status for current session (for banner display)
258
+ try {
259
+ const statusOutput = execSync(`node "${scriptPath}" status`, {
260
+ cwd: rootDir,
261
+ encoding: 'utf8',
262
+ stdio: ['pipe', 'pipe', 'pipe'],
263
+ });
264
+ const statusData = JSON.parse(statusOutput);
265
+ if (statusData.current) {
266
+ result.isMain = statusData.current.is_main === true;
267
+ result.nickname = statusData.current.nickname;
268
+ result.branch = statusData.current.branch;
269
+ result.sessionPath = statusData.current.path;
270
+ }
271
+ } catch (e) {
272
+ // Status failed
273
+ }
246
274
  } catch (e) {
247
275
  // Session system not available
248
276
  }
@@ -298,8 +326,8 @@ function checkDamageControl(rootDir) {
298
326
  const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
299
327
  if (settings.hooks?.PreToolUse && Array.isArray(settings.hooks.PreToolUse)) {
300
328
  // Check for damage-control hooks
301
- const hasDamageControlHooks = settings.hooks.PreToolUse.some(
302
- h => h.hooks?.some(hk => hk.command?.includes('damage-control'))
329
+ const hasDamageControlHooks = settings.hooks.PreToolUse.some(h =>
330
+ h.hooks?.some(hk => hk.command?.includes('damage-control'))
303
331
  );
304
332
  if (hasDamageControlHooks) {
305
333
  result.configured = true;
@@ -311,8 +339,8 @@ function checkDamageControl(rootDir) {
311
339
  result.hooksCount = dcHooks.length;
312
340
 
313
341
  // Check for enhanced mode (has prompt hook)
314
- const hasPromptHook = settings.hooks.PreToolUse.some(
315
- h => h.hooks?.some(hk => hk.type === 'prompt')
342
+ const hasPromptHook = settings.hooks.PreToolUse.some(h =>
343
+ h.hooks?.some(hk => hk.type === 'prompt')
316
344
  );
317
345
  if (hasPromptHook) {
318
346
  result.level = 'enhanced';
@@ -680,14 +708,24 @@ function formatTable(
680
708
  // Show update available notification (using vibrant colors)
681
709
  if (updateInfo.available && updateInfo.latest && !updateInfo.justUpdated) {
682
710
  lines.push(fullDivider());
683
- lines.push(fullRow(`${c.amber}↑${c.reset} Update available: ${c.softGold}v${updateInfo.latest}${c.reset}`, ''));
711
+ lines.push(
712
+ fullRow(
713
+ `${c.amber}↑${c.reset} Update available: ${c.softGold}v${updateInfo.latest}${c.reset}`,
714
+ ''
715
+ )
716
+ );
684
717
  lines.push(fullRow(` Run: ${c.skyBlue}npx agileflow update${c.reset}`, ''));
685
718
  }
686
719
 
687
720
  // Show "just updated" changelog
688
721
  if (updateInfo.justUpdated && updateInfo.changelog && updateInfo.changelog.length > 0) {
689
722
  lines.push(fullDivider());
690
- lines.push(fullRow(`${c.mintGreen}✨${c.reset} What's new in ${c.softGold}v${info.version}${c.reset}:`, ''));
723
+ lines.push(
724
+ fullRow(
725
+ `${c.mintGreen}✨${c.reset} What's new in ${c.softGold}v${info.version}${c.reset}:`,
726
+ ''
727
+ )
728
+ );
691
729
  for (const entry of updateInfo.changelog.slice(0, 2)) {
692
730
  lines.push(fullRow(` ${c.teal}•${c.reset} ${truncate(entry, W - 6)}`, ''));
693
731
  }
@@ -746,7 +784,9 @@ function formatTable(
746
784
 
747
785
  // Session cleanup
748
786
  const sessionStatus = session.cleared > 0 ? `cleared ${session.cleared} command(s)` : `clean`;
749
- lines.push(row('Session state', sessionStatus, c.lavender, session.cleared > 0 ? c.mintGreen : c.dim));
787
+ lines.push(
788
+ row('Session state', sessionStatus, c.lavender, session.cleared > 0 ? c.mintGreen : c.dim)
789
+ );
750
790
 
751
791
  // PreCompact status with version check
752
792
  if (precompact.configured && precompact.scriptExists) {
@@ -798,7 +838,8 @@ function formatTable(
798
838
  lines.push(row('Damage control', '⚠️ scripts missing', c.coral, c.coral));
799
839
  } else {
800
840
  const levelStr = damageControl.level || 'standard';
801
- const patternStr = damageControl.patternCount > 0 ? `${damageControl.patternCount} patterns` : '';
841
+ const patternStr =
842
+ damageControl.patternCount > 0 ? `${damageControl.patternCount} patterns` : '';
802
843
  const dcStatus = `🛡️ ${levelStr}${patternStr ? ` (${patternStr})` : ''}`;
803
844
  lines.push(row('Damage control', dcStatus, c.lavender, c.mintGreen));
804
845
  }
@@ -823,13 +864,50 @@ function formatTable(
823
864
  }
824
865
 
825
866
  // Last commit (colorful like obtain-context)
826
- lines.push(row('Last commit', `${c.peach}${info.commit}${c.reset} ${info.lastCommit}`, c.lavender, ''));
867
+ lines.push(
868
+ row('Last commit', `${c.peach}${info.commit}${c.reset} ${info.lastCommit}`, c.lavender, '')
869
+ );
827
870
 
828
871
  lines.push(bottomBorder);
829
872
 
830
873
  return lines.join('\n');
831
874
  }
832
875
 
876
+ // Format session banner for non-main sessions
877
+ function formatSessionBanner(parallelSessions) {
878
+ if (!parallelSessions.available || parallelSessions.isMain) {
879
+ return null;
880
+ }
881
+
882
+ const W = 62; // banner width
883
+ const lines = [];
884
+
885
+ // Get display name
886
+ const sessionName = parallelSessions.nickname
887
+ ? `SESSION ${parallelSessions.currentId} "${parallelSessions.nickname}"`
888
+ : `SESSION ${parallelSessions.currentId}`;
889
+
890
+ lines.push(`${c.dim}${box.tl}${box.h.repeat(W)}${box.tr}${c.reset}`);
891
+ lines.push(
892
+ `${c.dim}${box.v}${c.reset} ${c.teal}${c.bold}${pad(sessionName, W - 2)}${c.reset} ${c.dim}${box.v}${c.reset}`
893
+ );
894
+ lines.push(
895
+ `${c.dim}${box.v}${c.reset} ${c.slate}Branch:${c.reset} ${pad(parallelSessions.branch || 'unknown', W - 13)} ${c.dim}${box.v}${c.reset}`
896
+ );
897
+
898
+ // Show relative path to main
899
+ if (parallelSessions.sessionPath) {
900
+ const relPath = path.relative(parallelSessions.sessionPath, parallelSessions.mainPath) || '.';
901
+ lines.push(
902
+ `${c.dim}${box.v}${c.reset} ${c.slate}Main at:${c.reset} ${pad(relPath, W - 14)} ${c.dim}${box.v}${c.reset}`
903
+ );
904
+ }
905
+
906
+ lines.push(`${c.dim}${box.bl}${box.h.repeat(W)}${box.br}${c.reset}`);
907
+
908
+ return lines.join('\n');
909
+ }
910
+
833
911
  // Main
834
912
  async function main() {
835
913
  const rootDir = getProjectRoot();
@@ -863,16 +941,35 @@ async function main() {
863
941
  // Update check failed - continue without it
864
942
  }
865
943
 
944
+ // Show session banner FIRST if in a non-main session
945
+ const sessionBanner = formatSessionBanner(parallelSessions);
946
+ if (sessionBanner) {
947
+ console.log(sessionBanner);
948
+ }
949
+
866
950
  console.log(
867
- formatTable(info, archival, session, precompact, parallelSessions, updateInfo, expertise, damageControl)
951
+ formatTable(
952
+ info,
953
+ archival,
954
+ session,
955
+ precompact,
956
+ parallelSessions,
957
+ updateInfo,
958
+ expertise,
959
+ damageControl
960
+ )
868
961
  );
869
962
 
870
963
  // Show warning and tip if other sessions are active (vibrant colors)
871
964
  if (parallelSessions.otherActive > 0) {
872
965
  console.log('');
873
966
  console.log(`${c.amber}⚠️ Other Claude session(s) active in this repo.${c.reset}`);
874
- console.log(`${c.slate} Run ${c.skyBlue}/agileflow:session:status${c.reset}${c.slate} to see all sessions.${c.reset}`);
875
- console.log(`${c.slate} Run ${c.skyBlue}/agileflow:session:new${c.reset}${c.slate} to create isolated workspace.${c.reset}`);
967
+ console.log(
968
+ `${c.slate} Run ${c.skyBlue}/agileflow:session:status${c.reset}${c.slate} to see all sessions.${c.reset}`
969
+ );
970
+ console.log(
971
+ `${c.slate} Run ${c.skyBlue}/agileflow:session:new${c.reset}${c.slate} to create isolated workspace.${c.reset}`
972
+ );
876
973
  }
877
974
  }
878
975
 
@@ -139,7 +139,11 @@ function getDefaultPatterns() {
139
139
  { pattern: 'DROP\\s+(TABLE|DATABASE)', reason: 'DROP commands are destructive' },
140
140
  { pattern: 'DELETE\\s+FROM\\s+\\w+\\s*;', reason: 'DELETE without WHERE clause' },
141
141
  { pattern: 'TRUNCATE\\s+(TABLE\\s+)?\\w+', reason: 'TRUNCATE removes all data' },
142
- { pattern: 'git\\s+push\\s+.*--force', reason: 'Force push can overwrite history', ask: true },
142
+ {
143
+ pattern: 'git\\s+push\\s+.*--force',
144
+ reason: 'Force push can overwrite history',
145
+ ask: true,
146
+ },
143
147
  { pattern: 'git\\s+reset\\s+--hard', reason: 'Hard reset discards changes', ask: true },
144
148
  ],
145
149
  askPatterns: [
@@ -159,10 +163,7 @@ function getDefaultPatterns() {
159
163
  */
160
164
  function checkCommand(command, patterns) {
161
165
  // Combine all pattern sources
162
- const allPatterns = [
163
- ...(patterns.bashToolPatterns || []),
164
- ...(patterns.agileflowPatterns || []),
165
- ];
166
+ const allPatterns = [...(patterns.bashToolPatterns || []), ...(patterns.agileflowPatterns || [])];
166
167
 
167
168
  // Check block/ask patterns
168
169
  for (const p of allPatterns) {
@@ -181,7 +182,7 @@ function checkCommand(command, patterns) {
181
182
  }
182
183
 
183
184
  // Check ask-only patterns
184
- for (const p of (patterns.askPatterns || [])) {
185
+ for (const p of patterns.askPatterns || []) {
185
186
  try {
186
187
  const regex = new RegExp(p.pattern, 'i');
187
188
  if (regex.test(command)) {
@@ -114,27 +114,9 @@ function parsePathRules(content) {
114
114
  */
115
115
  function getDefaultPathRules() {
116
116
  return {
117
- zeroAccessPaths: [
118
- '~/.ssh/',
119
- '~/.aws/credentials',
120
- '.env',
121
- '.env.local',
122
- '.env.production',
123
- ],
124
- readOnlyPaths: [
125
- '/etc/',
126
- '~/.bashrc',
127
- '~/.zshrc',
128
- 'package-lock.json',
129
- 'yarn.lock',
130
- '.git/',
131
- ],
132
- noDeletePaths: [
133
- '.agileflow/',
134
- '.claude/',
135
- 'docs/09-agents/status.json',
136
- 'CLAUDE.md',
137
- ],
117
+ zeroAccessPaths: ['~/.ssh/', '~/.aws/credentials', '.env', '.env.local', '.env.production'],
118
+ readOnlyPaths: ['/etc/', '~/.bashrc', '~/.zshrc', 'package-lock.json', 'yarn.lock', '.git/'],
119
+ noDeletePaths: ['.agileflow/', '.claude/', 'docs/09-agents/status.json', 'CLAUDE.md'],
138
120
  };
139
121
  }
140
122
 
@@ -252,9 +234,7 @@ function main() {
252
234
  }
253
235
 
254
236
  // Resolve to absolute path
255
- const absolutePath = path.isAbsolute(filePath)
256
- ? filePath
257
- : path.join(projectDir, filePath);
237
+ const absolutePath = path.isAbsolute(filePath) ? filePath : path.join(projectDir, filePath);
258
238
 
259
239
  // Load rules
260
240
  const rules = loadPathRules(projectDir);