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.
- package/README.md +6 -6
- package/package.json +1 -1
- package/scripts/agent-loop.js +765 -0
- package/scripts/agileflow-configure.js +129 -18
- package/scripts/agileflow-welcome.js +113 -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 +38 -125
- package/scripts/damage-control-edit.js +22 -165
- package/scripts/damage-control-write.js +22 -165
- package/scripts/get-env.js +6 -6
- package/scripts/lib/damage-control-utils.js +251 -0
- package/scripts/obtain-context.js +103 -37
- package/scripts/ralph-loop.js +243 -31
- package/scripts/screenshot-verifier.js +4 -2
- package/scripts/session-manager.js +434 -20
- package/src/core/agents/configuration-visual-e2e.md +300 -0
- package/src/core/agents/orchestrator.md +166 -0
- package/src/core/commands/babysit.md +61 -15
- package/src/core/commands/configure.md +408 -99
- package/src/core/commands/session/end.md +332 -103
- 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/src/core/commands/setup/visual-e2e.md +0 -462
|
@@ -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: [
|
|
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: [
|
|
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: {
|
|
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 =>
|
|
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 =>
|
|
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 =>
|
|
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 =
|
|
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
|
|
373
|
-
|
|
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[
|
|
376
|
-
status.features[
|
|
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 = [
|
|
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(
|
|
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='))
|
|
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
|
-
|
|
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
|
}
|
|
@@ -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
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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 =
|
|
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(
|
|
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(
|
|
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(
|
|
875
|
-
|
|
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
|
-
{
|
|
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);
|