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 +6 -6
- package/package.json +1 -1
- package/scripts/agileflow-configure.js +126 -17
- package/scripts/agileflow-welcome.js +77 -98
- package/scripts/auto-self-improve.js +23 -45
- package/scripts/check-update.js +35 -42
- package/scripts/damage-control/bash-tool-damage-control.js +258 -0
- package/scripts/damage-control/edit-tool-damage-control.js +259 -0
- package/scripts/damage-control/patterns.yaml +227 -0
- package/scripts/damage-control/write-tool-damage-control.js +254 -0
- 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 +67 -37
- package/scripts/ralph-loop.js +199 -63
- package/scripts/screenshot-verifier.js +215 -0
- package/scripts/session-manager.js +12 -33
- package/src/core/agents/configuration-damage-control.md +248 -0
- package/src/core/commands/babysit.md +30 -2
- package/src/core/commands/configure.md +46 -9
- package/src/core/commands/setup/visual-e2e.md +462 -0
- package/src/core/experts/documentation/expertise.yaml +25 -0
- package/src/core/skills/_learnings/code-review.yaml +118 -0
- package/src/core/skills/_learnings/story-writer.yaml +71 -0
- package/tools/cli/commands/start.js +19 -21
- package/tools/cli/installers/ide/claude-code.js +140 -0
- package/tools/cli/tui/Dashboard.js +3 -4
- package/tools/postinstall.js +1 -9
package/README.md
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/agileflow)
|
|
6
|
-
[](docs/04-architecture/commands.md)
|
|
7
|
+
[](docs/04-architecture/subagents.md)
|
|
8
8
|
[](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) |
|
|
69
|
-
| [Agents/Experts](docs/04-architecture/subagents.md) |
|
|
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
|
|
80
|
-
- [Agents/Experts](docs/04-architecture/subagents.md) -
|
|
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
|
@@ -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
|
|
|
@@ -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
|
-
|
|
242
|
-
//
|
|
243
|
-
if (
|
|
244
|
-
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);
|
|
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:
|
|
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
|
|
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
|
|
372
|
-
const
|
|
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
|
|
375
|
-
'damage-control
|
|
376
|
-
'damage-control
|
|
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(
|
|
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
|
-
//
|
|
389
|
-
const
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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 =
|
|
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(
|
|
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(
|
|
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(
|
|
928
|
-
|
|
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
|
-
//
|
|
25
|
-
const c =
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
92
|
-
|
|
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
|
-
|
|
236
|
-
|
|
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
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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
|