agileflow 2.79.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/package.json +1 -1
- package/scripts/agileflow-configure.js +126 -17
- package/scripts/agileflow-welcome.js +48 -16
- package/scripts/damage-control/bash-tool-damage-control.js +7 -6
- package/scripts/damage-control/edit-tool-damage-control.js +4 -24
- package/scripts/damage-control/patterns.yaml +32 -32
- package/scripts/damage-control/write-tool-damage-control.js +4 -24
- package/scripts/damage-control-bash.js +28 -22
- package/scripts/damage-control-edit.js +6 -12
- package/scripts/damage-control-write.js +6 -12
- package/scripts/get-env.js +6 -6
- package/scripts/obtain-context.js +47 -36
- package/scripts/ralph-loop.js +14 -6
- package/scripts/screenshot-verifier.js +4 -2
- package/src/core/commands/configure.md +46 -9
- package/src/core/experts/documentation/expertise.yaml +25 -0
- package/tools/cli/commands/start.js +19 -21
- package/tools/cli/installers/ide/claude-code.js +32 -19
- package/tools/cli/tui/Dashboard.js +3 -4
- package/tools/postinstall.js +1 -9
package/package.json
CHANGED
|
@@ -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: [
|
|
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: [
|
|
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: {
|
|
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 =>
|
|
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 =>
|
|
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 =>
|
|
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 =
|
|
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
|
|
373
|
-
|
|
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[
|
|
376
|
-
status.features[
|
|
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 = [
|
|
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(
|
|
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
|
|
|
@@ -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
|
-
|
|
184
|
-
//
|
|
185
|
-
if (
|
|
186
|
-
result.
|
|
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
|
}
|
|
@@ -298,8 +302,8 @@ function checkDamageControl(rootDir) {
|
|
|
298
302
|
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
299
303
|
if (settings.hooks?.PreToolUse && Array.isArray(settings.hooks.PreToolUse)) {
|
|
300
304
|
// Check for damage-control hooks
|
|
301
|
-
const hasDamageControlHooks = settings.hooks.PreToolUse.some(
|
|
302
|
-
h
|
|
305
|
+
const hasDamageControlHooks = settings.hooks.PreToolUse.some(h =>
|
|
306
|
+
h.hooks?.some(hk => hk.command?.includes('damage-control'))
|
|
303
307
|
);
|
|
304
308
|
if (hasDamageControlHooks) {
|
|
305
309
|
result.configured = true;
|
|
@@ -311,8 +315,8 @@ function checkDamageControl(rootDir) {
|
|
|
311
315
|
result.hooksCount = dcHooks.length;
|
|
312
316
|
|
|
313
317
|
// Check for enhanced mode (has prompt hook)
|
|
314
|
-
const hasPromptHook = settings.hooks.PreToolUse.some(
|
|
315
|
-
h
|
|
318
|
+
const hasPromptHook = settings.hooks.PreToolUse.some(h =>
|
|
319
|
+
h.hooks?.some(hk => hk.type === 'prompt')
|
|
316
320
|
);
|
|
317
321
|
if (hasPromptHook) {
|
|
318
322
|
result.level = 'enhanced';
|
|
@@ -680,14 +684,24 @@ function formatTable(
|
|
|
680
684
|
// Show update available notification (using vibrant colors)
|
|
681
685
|
if (updateInfo.available && updateInfo.latest && !updateInfo.justUpdated) {
|
|
682
686
|
lines.push(fullDivider());
|
|
683
|
-
lines.push(
|
|
687
|
+
lines.push(
|
|
688
|
+
fullRow(
|
|
689
|
+
`${c.amber}↑${c.reset} Update available: ${c.softGold}v${updateInfo.latest}${c.reset}`,
|
|
690
|
+
''
|
|
691
|
+
)
|
|
692
|
+
);
|
|
684
693
|
lines.push(fullRow(` Run: ${c.skyBlue}npx agileflow update${c.reset}`, ''));
|
|
685
694
|
}
|
|
686
695
|
|
|
687
696
|
// Show "just updated" changelog
|
|
688
697
|
if (updateInfo.justUpdated && updateInfo.changelog && updateInfo.changelog.length > 0) {
|
|
689
698
|
lines.push(fullDivider());
|
|
690
|
-
lines.push(
|
|
699
|
+
lines.push(
|
|
700
|
+
fullRow(
|
|
701
|
+
`${c.mintGreen}✨${c.reset} What's new in ${c.softGold}v${info.version}${c.reset}:`,
|
|
702
|
+
''
|
|
703
|
+
)
|
|
704
|
+
);
|
|
691
705
|
for (const entry of updateInfo.changelog.slice(0, 2)) {
|
|
692
706
|
lines.push(fullRow(` ${c.teal}•${c.reset} ${truncate(entry, W - 6)}`, ''));
|
|
693
707
|
}
|
|
@@ -746,7 +760,9 @@ function formatTable(
|
|
|
746
760
|
|
|
747
761
|
// Session cleanup
|
|
748
762
|
const sessionStatus = session.cleared > 0 ? `cleared ${session.cleared} command(s)` : `clean`;
|
|
749
|
-
lines.push(
|
|
763
|
+
lines.push(
|
|
764
|
+
row('Session state', sessionStatus, c.lavender, session.cleared > 0 ? c.mintGreen : c.dim)
|
|
765
|
+
);
|
|
750
766
|
|
|
751
767
|
// PreCompact status with version check
|
|
752
768
|
if (precompact.configured && precompact.scriptExists) {
|
|
@@ -798,7 +814,8 @@ function formatTable(
|
|
|
798
814
|
lines.push(row('Damage control', '⚠️ scripts missing', c.coral, c.coral));
|
|
799
815
|
} else {
|
|
800
816
|
const levelStr = damageControl.level || 'standard';
|
|
801
|
-
const patternStr =
|
|
817
|
+
const patternStr =
|
|
818
|
+
damageControl.patternCount > 0 ? `${damageControl.patternCount} patterns` : '';
|
|
802
819
|
const dcStatus = `🛡️ ${levelStr}${patternStr ? ` (${patternStr})` : ''}`;
|
|
803
820
|
lines.push(row('Damage control', dcStatus, c.lavender, c.mintGreen));
|
|
804
821
|
}
|
|
@@ -823,7 +840,9 @@ function formatTable(
|
|
|
823
840
|
}
|
|
824
841
|
|
|
825
842
|
// Last commit (colorful like obtain-context)
|
|
826
|
-
lines.push(
|
|
843
|
+
lines.push(
|
|
844
|
+
row('Last commit', `${c.peach}${info.commit}${c.reset} ${info.lastCommit}`, c.lavender, '')
|
|
845
|
+
);
|
|
827
846
|
|
|
828
847
|
lines.push(bottomBorder);
|
|
829
848
|
|
|
@@ -864,15 +883,28 @@ async function main() {
|
|
|
864
883
|
}
|
|
865
884
|
|
|
866
885
|
console.log(
|
|
867
|
-
formatTable(
|
|
886
|
+
formatTable(
|
|
887
|
+
info,
|
|
888
|
+
archival,
|
|
889
|
+
session,
|
|
890
|
+
precompact,
|
|
891
|
+
parallelSessions,
|
|
892
|
+
updateInfo,
|
|
893
|
+
expertise,
|
|
894
|
+
damageControl
|
|
895
|
+
)
|
|
868
896
|
);
|
|
869
897
|
|
|
870
898
|
// Show warning and tip if other sessions are active (vibrant colors)
|
|
871
899
|
if (parallelSessions.otherActive > 0) {
|
|
872
900
|
console.log('');
|
|
873
901
|
console.log(`${c.amber}⚠️ Other Claude session(s) active in this repo.${c.reset}`);
|
|
874
|
-
console.log(
|
|
875
|
-
|
|
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
|
+
);
|
|
876
908
|
}
|
|
877
909
|
}
|
|
878
910
|
|
|
@@ -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
|
-
{
|
|
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
|
|
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
|
-
|
|
119
|
-
|
|
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);
|
|
@@ -20,87 +20,87 @@
|
|
|
20
20
|
bashToolPatterns:
|
|
21
21
|
# Recursive/force deletion
|
|
22
22
|
- pattern: '\brm\s+-[rRf]'
|
|
23
|
-
reason:
|
|
23
|
+
reason: 'rm with recursive or force flags can destroy entire directories'
|
|
24
24
|
|
|
25
25
|
- pattern: '\brm\s+.*--no-preserve-root'
|
|
26
|
-
reason:
|
|
26
|
+
reason: 'rm with --no-preserve-root is catastrophically dangerous'
|
|
27
27
|
|
|
28
28
|
- pattern: '\brm\s+-rf\s+/'
|
|
29
|
-
reason:
|
|
29
|
+
reason: 'rm -rf on root directory would destroy the entire system'
|
|
30
30
|
|
|
31
31
|
# SQL destructive commands without WHERE clause
|
|
32
32
|
- pattern: 'DELETE\s+FROM\s+\w+\s*;'
|
|
33
|
-
reason:
|
|
33
|
+
reason: 'DELETE without WHERE clause would delete all records'
|
|
34
34
|
|
|
35
35
|
- pattern: 'TRUNCATE\s+(TABLE\s+)?\w+'
|
|
36
|
-
reason:
|
|
36
|
+
reason: 'TRUNCATE removes all data from table'
|
|
37
37
|
|
|
38
38
|
- pattern: 'DROP\s+(TABLE|DATABASE|SCHEMA|INDEX)'
|
|
39
|
-
reason:
|
|
39
|
+
reason: 'DROP commands permanently destroy database objects'
|
|
40
40
|
|
|
41
41
|
# Git force operations
|
|
42
42
|
- pattern: 'git\s+push\s+.*--force'
|
|
43
|
-
reason:
|
|
43
|
+
reason: 'Force push can overwrite remote history'
|
|
44
44
|
ask: true
|
|
45
45
|
|
|
46
46
|
- pattern: 'git\s+push\s+.*-f\b'
|
|
47
|
-
reason:
|
|
47
|
+
reason: 'Force push can overwrite remote history'
|
|
48
48
|
ask: true
|
|
49
49
|
|
|
50
50
|
- pattern: 'git\s+reset\s+--hard'
|
|
51
|
-
reason:
|
|
51
|
+
reason: 'Hard reset discards uncommitted changes'
|
|
52
52
|
ask: true
|
|
53
53
|
|
|
54
54
|
# Format/wipe operations
|
|
55
55
|
- pattern: '\bmkfs\b'
|
|
56
|
-
reason:
|
|
56
|
+
reason: 'mkfs formats filesystems, destroying all data'
|
|
57
57
|
|
|
58
58
|
- pattern: '\bdd\s+.*of=/dev/'
|
|
59
|
-
reason:
|
|
59
|
+
reason: 'dd writing to device can destroy disk data'
|
|
60
60
|
|
|
61
61
|
- pattern: '\bshred\b'
|
|
62
|
-
reason:
|
|
62
|
+
reason: 'shred permanently destroys file data'
|
|
63
63
|
|
|
64
64
|
# Credential/secret exposure
|
|
65
65
|
- pattern: 'cat\s+.*\.env'
|
|
66
|
-
reason:
|
|
66
|
+
reason: 'Displaying .env may expose secrets'
|
|
67
67
|
ask: true
|
|
68
68
|
|
|
69
69
|
- pattern: 'cat\s+.*/\.ssh/'
|
|
70
|
-
reason:
|
|
70
|
+
reason: 'Displaying SSH keys is a security risk'
|
|
71
71
|
|
|
72
72
|
- pattern: 'cat\s+.*/credentials'
|
|
73
|
-
reason:
|
|
73
|
+
reason: 'Displaying credentials files is a security risk'
|
|
74
74
|
|
|
75
75
|
# Cloud CLI destructive operations
|
|
76
76
|
- pattern: 'aws\s+s3\s+rm\s+--recursive'
|
|
77
|
-
reason:
|
|
77
|
+
reason: 'Recursive S3 delete can destroy entire buckets'
|
|
78
78
|
ask: true
|
|
79
79
|
|
|
80
80
|
- pattern: 'aws\s+ec2\s+terminate-instances'
|
|
81
|
-
reason:
|
|
81
|
+
reason: 'Terminating EC2 instances is irreversible'
|
|
82
82
|
ask: true
|
|
83
83
|
|
|
84
84
|
- pattern: 'gcloud\s+.*delete'
|
|
85
|
-
reason:
|
|
85
|
+
reason: 'GCloud delete operations may be destructive'
|
|
86
86
|
ask: true
|
|
87
87
|
|
|
88
88
|
# Docker cleanup commands
|
|
89
89
|
- pattern: 'docker\s+system\s+prune\s+-a'
|
|
90
|
-
reason:
|
|
90
|
+
reason: 'Docker prune -a removes all unused images'
|
|
91
91
|
ask: true
|
|
92
92
|
|
|
93
93
|
- pattern: 'docker\s+volume\s+rm'
|
|
94
|
-
reason:
|
|
94
|
+
reason: 'Docker volume removal may delete persistent data'
|
|
95
95
|
ask: true
|
|
96
96
|
|
|
97
97
|
# npm/package manager dangerous commands
|
|
98
98
|
- pattern: 'npm\s+unpublish'
|
|
99
|
-
reason:
|
|
99
|
+
reason: 'npm unpublish can break dependent packages'
|
|
100
100
|
ask: true
|
|
101
101
|
|
|
102
102
|
- pattern: 'npm\s+deprecate'
|
|
103
|
-
reason:
|
|
103
|
+
reason: 'npm deprecate affects package visibility'
|
|
104
104
|
ask: true
|
|
105
105
|
|
|
106
106
|
# ============================================================================
|
|
@@ -110,19 +110,19 @@ bashToolPatterns:
|
|
|
110
110
|
|
|
111
111
|
askPatterns:
|
|
112
112
|
- pattern: 'DELETE\s+FROM\s+\w+\s+WHERE'
|
|
113
|
-
reason:
|
|
113
|
+
reason: 'Deleting specific records - confirm data is correct'
|
|
114
114
|
|
|
115
115
|
- pattern: 'UPDATE\s+\w+\s+SET'
|
|
116
|
-
reason:
|
|
116
|
+
reason: 'Updating records - confirm scope is correct'
|
|
117
117
|
|
|
118
118
|
- pattern: 'npm\s+publish'
|
|
119
|
-
reason:
|
|
119
|
+
reason: 'Publishing to npm is permanent'
|
|
120
120
|
|
|
121
121
|
- pattern: 'git\s+tag\s+-d'
|
|
122
|
-
reason:
|
|
122
|
+
reason: 'Deleting git tags'
|
|
123
123
|
|
|
124
124
|
- pattern: 'kubectl\s+delete'
|
|
125
|
-
reason:
|
|
125
|
+
reason: 'Kubernetes delete operations'
|
|
126
126
|
|
|
127
127
|
# ============================================================================
|
|
128
128
|
# PATH PROTECTION
|
|
@@ -193,17 +193,17 @@ noDeletePaths:
|
|
|
193
193
|
agileflowPatterns:
|
|
194
194
|
# Protect AgileFlow infrastructure
|
|
195
195
|
- pattern: 'rm.*\.agileflow'
|
|
196
|
-
reason:
|
|
196
|
+
reason: 'Deleting .agileflow would break AgileFlow installation'
|
|
197
197
|
|
|
198
198
|
- pattern: 'rm.*\.claude'
|
|
199
|
-
reason:
|
|
199
|
+
reason: 'Deleting .claude would break Claude Code configuration'
|
|
200
200
|
|
|
201
201
|
- pattern: 'rm.*status\.json'
|
|
202
|
-
reason:
|
|
202
|
+
reason: 'Deleting status.json would lose story tracking data'
|
|
203
203
|
|
|
204
204
|
# Dangerous npm operations in AgileFlow context
|
|
205
205
|
- pattern: 'npm\s+uninstall\s+agileflow'
|
|
206
|
-
reason:
|
|
206
|
+
reason: 'Uninstalling AgileFlow - confirm this is intentional'
|
|
207
207
|
ask: true
|
|
208
208
|
|
|
209
209
|
# ============================================================================
|
|
@@ -224,4 +224,4 @@ config:
|
|
|
224
224
|
|
|
225
225
|
# Enable/disable prompt hooks (AI-based evaluation)
|
|
226
226
|
promptHooksEnabled: false
|
|
227
|
-
promptHookMessage:
|
|
227
|
+
promptHookMessage: 'Evaluate if this command could cause irreversible damage. Block if dangerous.'
|