agileflow 2.84.1 → 2.85.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/CHANGELOG.md +10 -0
- package/package.json +1 -1
- package/scripts/agileflow-welcome.js +385 -124
- package/scripts/obtain-context.js +7 -6
- package/scripts/session-boundary.js +138 -0
- package/scripts/session-manager.js +224 -0
- package/src/core/commands/session/new.md +80 -49
- package/src/core/commands/session/resume.md +40 -16
|
@@ -21,7 +21,10 @@ const { execSync } = require('child_process');
|
|
|
21
21
|
const { c: C, box } = require('../lib/colors');
|
|
22
22
|
const { isValidCommandName } = require('../lib/validate');
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
// Claude Code's Bash tool truncates around 30K chars, but ANSI codes and
|
|
25
|
+
// box-drawing characters (╭╮╰╯─│) are multi-byte UTF-8, so we need buffer.
|
|
26
|
+
// Summary table should be the LAST thing visible before truncation.
|
|
27
|
+
const DISPLAY_LIMIT = 29200;
|
|
25
28
|
|
|
26
29
|
// Optional: Register command for PreCompact context preservation
|
|
27
30
|
const commandName = process.argv[2];
|
|
@@ -300,8 +303,6 @@ function generateSummary() {
|
|
|
300
303
|
);
|
|
301
304
|
|
|
302
305
|
summary += bottomBorder;
|
|
303
|
-
summary += '\n';
|
|
304
|
-
summary += `${C.dim}Full context continues below (Claude sees all)...${C.reset}\n\n`;
|
|
305
306
|
|
|
306
307
|
return summary;
|
|
307
308
|
}
|
|
@@ -706,11 +707,11 @@ if (fullContent.length <= cutoffPoint) {
|
|
|
706
707
|
console.log(fullContent);
|
|
707
708
|
console.log(summary);
|
|
708
709
|
} else {
|
|
709
|
-
//
|
|
710
|
+
// Output content up to cutoff, then summary as the LAST visible thing.
|
|
711
|
+
// Don't output contentAfter - it would bleed into visible area before truncation,
|
|
712
|
+
// and Claude only sees ~30K chars from Bash anyway.
|
|
710
713
|
const contentBefore = fullContent.substring(0, cutoffPoint);
|
|
711
|
-
const contentAfter = fullContent.substring(cutoffPoint);
|
|
712
714
|
|
|
713
715
|
console.log(contentBefore);
|
|
714
716
|
console.log(summary);
|
|
715
|
-
console.log(contentAfter);
|
|
716
717
|
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* session-boundary.js - PreToolUse hook for Edit/Write session isolation
|
|
4
|
+
*
|
|
5
|
+
* Prevents Claude from editing files outside the active session directory.
|
|
6
|
+
* Used with PreToolUse:Edit and PreToolUse:Write hooks.
|
|
7
|
+
*
|
|
8
|
+
* Exit codes:
|
|
9
|
+
* 0 = Allow the operation
|
|
10
|
+
* 2 = Block with message (shown to Claude)
|
|
11
|
+
*
|
|
12
|
+
* Input: JSON on stdin with tool_input.file_path
|
|
13
|
+
* Output: Error message to stderr if blocking
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
const path = require('path');
|
|
18
|
+
|
|
19
|
+
// Inline colors (no external dependency)
|
|
20
|
+
const c = {
|
|
21
|
+
coral: '\x1b[38;5;203m',
|
|
22
|
+
dim: '\x1b[2m',
|
|
23
|
+
reset: '\x1b[0m',
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const STDIN_TIMEOUT_MS = 4000;
|
|
27
|
+
|
|
28
|
+
// Find project root by looking for .agileflow directory
|
|
29
|
+
function findProjectRoot() {
|
|
30
|
+
let dir = process.cwd();
|
|
31
|
+
while (dir !== '/') {
|
|
32
|
+
if (fs.existsSync(path.join(dir, '.agileflow'))) {
|
|
33
|
+
return dir;
|
|
34
|
+
}
|
|
35
|
+
if (fs.existsSync(path.join(dir, 'docs', '09-agents'))) {
|
|
36
|
+
return dir;
|
|
37
|
+
}
|
|
38
|
+
dir = path.dirname(dir);
|
|
39
|
+
}
|
|
40
|
+
return process.cwd();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const ROOT = findProjectRoot();
|
|
44
|
+
const SESSION_STATE_PATH = path.join(ROOT, 'docs', '09-agents', 'session-state.json');
|
|
45
|
+
|
|
46
|
+
// Get active session from session-state.json
|
|
47
|
+
function getActiveSession() {
|
|
48
|
+
try {
|
|
49
|
+
if (!fs.existsSync(SESSION_STATE_PATH)) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
const data = JSON.parse(fs.readFileSync(SESSION_STATE_PATH, 'utf8'));
|
|
53
|
+
return data.active_session || null;
|
|
54
|
+
} catch (e) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Check if filePath is inside sessionPath
|
|
60
|
+
function isInsideSession(filePath, sessionPath) {
|
|
61
|
+
const normalizedFile = path.resolve(filePath);
|
|
62
|
+
const normalizedSession = path.resolve(sessionPath);
|
|
63
|
+
|
|
64
|
+
return normalizedFile.startsWith(normalizedSession + path.sep) ||
|
|
65
|
+
normalizedFile === normalizedSession;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Output blocked message
|
|
69
|
+
function outputBlocked(filePath, activeSession) {
|
|
70
|
+
const sessionName = activeSession.nickname
|
|
71
|
+
? `"${activeSession.nickname}"`
|
|
72
|
+
: `Session ${activeSession.id}`;
|
|
73
|
+
|
|
74
|
+
console.error(`${c.coral}[SESSION BOUNDARY]${c.reset} Edit blocked`);
|
|
75
|
+
console.error(`${c.dim}File: ${filePath}${c.reset}`);
|
|
76
|
+
console.error(`${c.dim}Active session: ${sessionName} (${activeSession.path})${c.reset}`);
|
|
77
|
+
console.error('');
|
|
78
|
+
console.error(`${c.dim}The file is outside the active session directory.${c.reset}`);
|
|
79
|
+
console.error(`${c.dim}Use /agileflow:session:resume to switch sessions first.${c.reset}`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Main logic - run with stdin events (async)
|
|
83
|
+
function main() {
|
|
84
|
+
let inputData = '';
|
|
85
|
+
|
|
86
|
+
process.stdin.setEncoding('utf8');
|
|
87
|
+
|
|
88
|
+
process.stdin.on('data', chunk => {
|
|
89
|
+
inputData += chunk;
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
process.stdin.on('end', () => {
|
|
93
|
+
try {
|
|
94
|
+
// Parse tool input from Claude Code
|
|
95
|
+
const hookData = JSON.parse(inputData);
|
|
96
|
+
const filePath = hookData?.tool_input?.file_path;
|
|
97
|
+
|
|
98
|
+
if (!filePath) {
|
|
99
|
+
// No file path in input - allow
|
|
100
|
+
process.exit(0);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Get active session
|
|
104
|
+
const activeSession = getActiveSession();
|
|
105
|
+
|
|
106
|
+
if (!activeSession || !activeSession.path) {
|
|
107
|
+
// No active session set - allow all (normal behavior)
|
|
108
|
+
process.exit(0);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Check if file is inside active session
|
|
112
|
+
if (isInsideSession(filePath, activeSession.path)) {
|
|
113
|
+
// File is inside active session - allow
|
|
114
|
+
process.exit(0);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// File is OUTSIDE active session - BLOCK
|
|
118
|
+
outputBlocked(filePath, activeSession);
|
|
119
|
+
process.exit(2);
|
|
120
|
+
|
|
121
|
+
} catch (e) {
|
|
122
|
+
// Parse error or other issue - fail open
|
|
123
|
+
process.exit(0);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// Handle no stdin (direct invocation)
|
|
128
|
+
process.stdin.on('error', () => {
|
|
129
|
+
process.exit(0);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// Set timeout to prevent hanging
|
|
133
|
+
setTimeout(() => {
|
|
134
|
+
process.exit(0);
|
|
135
|
+
}, STDIN_TIMEOUT_MS);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
main();
|
|
@@ -786,6 +786,79 @@ function main() {
|
|
|
786
786
|
break;
|
|
787
787
|
}
|
|
788
788
|
|
|
789
|
+
// PERFORMANCE: Combined command for welcome script (saves ~200ms from 3 subprocess calls)
|
|
790
|
+
case 'full-status': {
|
|
791
|
+
const nickname = args[1] || null;
|
|
792
|
+
const cwd = process.cwd();
|
|
793
|
+
|
|
794
|
+
// Register in single pass (combines register + count + status)
|
|
795
|
+
const registry = loadRegistry();
|
|
796
|
+
const cleaned = cleanupStaleLocks(registry);
|
|
797
|
+
const branch = getCurrentBranch();
|
|
798
|
+
const story = getCurrentStory();
|
|
799
|
+
const pid = process.ppid || process.pid;
|
|
800
|
+
|
|
801
|
+
// Find or create session
|
|
802
|
+
let sessionId = null;
|
|
803
|
+
let isNew = false;
|
|
804
|
+
for (const [id, session] of Object.entries(registry.sessions)) {
|
|
805
|
+
if (session.path === cwd) {
|
|
806
|
+
sessionId = id;
|
|
807
|
+
break;
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
if (sessionId) {
|
|
812
|
+
// Update existing
|
|
813
|
+
registry.sessions[sessionId].branch = branch;
|
|
814
|
+
registry.sessions[sessionId].story = story ? story.id : null;
|
|
815
|
+
registry.sessions[sessionId].last_active = new Date().toISOString();
|
|
816
|
+
if (nickname) registry.sessions[sessionId].nickname = nickname;
|
|
817
|
+
writeLock(sessionId, pid);
|
|
818
|
+
} else {
|
|
819
|
+
// Create new
|
|
820
|
+
sessionId = String(registry.next_id);
|
|
821
|
+
registry.next_id++;
|
|
822
|
+
registry.sessions[sessionId] = {
|
|
823
|
+
path: cwd,
|
|
824
|
+
branch,
|
|
825
|
+
story: story ? story.id : null,
|
|
826
|
+
nickname: nickname || null,
|
|
827
|
+
created: new Date().toISOString(),
|
|
828
|
+
last_active: new Date().toISOString(),
|
|
829
|
+
is_main: cwd === ROOT,
|
|
830
|
+
};
|
|
831
|
+
writeLock(sessionId, pid);
|
|
832
|
+
isNew = true;
|
|
833
|
+
}
|
|
834
|
+
saveRegistry(registry);
|
|
835
|
+
|
|
836
|
+
// Build session list and counts
|
|
837
|
+
const sessions = [];
|
|
838
|
+
let otherActive = 0;
|
|
839
|
+
for (const [id, session] of Object.entries(registry.sessions)) {
|
|
840
|
+
const active = isSessionActive(id);
|
|
841
|
+
const isCurrent = session.path === cwd;
|
|
842
|
+
sessions.push({ id, ...session, active, current: isCurrent });
|
|
843
|
+
if (active && !isCurrent) otherActive++;
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
const current = sessions.find(s => s.current) || null;
|
|
847
|
+
|
|
848
|
+
console.log(
|
|
849
|
+
JSON.stringify({
|
|
850
|
+
registered: true,
|
|
851
|
+
id: sessionId,
|
|
852
|
+
isNew,
|
|
853
|
+
current,
|
|
854
|
+
otherActive,
|
|
855
|
+
total: sessions.length,
|
|
856
|
+
cleaned,
|
|
857
|
+
})
|
|
858
|
+
);
|
|
859
|
+
break;
|
|
860
|
+
}
|
|
861
|
+
|
|
789
862
|
case 'check-merge': {
|
|
790
863
|
const sessionId = args[1];
|
|
791
864
|
if (!sessionId) {
|
|
@@ -888,6 +961,29 @@ function main() {
|
|
|
888
961
|
break;
|
|
889
962
|
}
|
|
890
963
|
|
|
964
|
+
case 'switch': {
|
|
965
|
+
const sessionIdOrNickname = args[1];
|
|
966
|
+
if (!sessionIdOrNickname) {
|
|
967
|
+
console.log(JSON.stringify({ success: false, error: 'Session ID or nickname required' }));
|
|
968
|
+
return;
|
|
969
|
+
}
|
|
970
|
+
const result = switchSession(sessionIdOrNickname);
|
|
971
|
+
console.log(JSON.stringify(result, null, 2));
|
|
972
|
+
break;
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
case 'active': {
|
|
976
|
+
const result = getActiveSession();
|
|
977
|
+
console.log(JSON.stringify(result, null, 2));
|
|
978
|
+
break;
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
case 'clear-active': {
|
|
982
|
+
const result = clearActiveSession();
|
|
983
|
+
console.log(JSON.stringify(result));
|
|
984
|
+
break;
|
|
985
|
+
}
|
|
986
|
+
|
|
891
987
|
case 'help':
|
|
892
988
|
default:
|
|
893
989
|
console.log(`
|
|
@@ -901,6 +997,10 @@ ${c.cyan}Commands:${c.reset}
|
|
|
901
997
|
count Count other active sessions
|
|
902
998
|
delete <id> [--remove-worktree] Delete session
|
|
903
999
|
status Get current session status
|
|
1000
|
+
full-status Combined register+count+status (optimized)
|
|
1001
|
+
switch <id|nickname> Switch active session context (for /add-dir)
|
|
1002
|
+
active Get currently switched session (if any)
|
|
1003
|
+
clear-active Clear switched session (back to main)
|
|
904
1004
|
check-merge <id> Check if session is mergeable to main
|
|
905
1005
|
merge-preview <id> Preview commits/files to be merged
|
|
906
1006
|
integrate <id> [opts] Merge session to main and cleanup
|
|
@@ -1392,6 +1492,126 @@ function getMergeHistory() {
|
|
|
1392
1492
|
}
|
|
1393
1493
|
}
|
|
1394
1494
|
|
|
1495
|
+
// Session state file path
|
|
1496
|
+
const SESSION_STATE_PATH = path.join(ROOT, 'docs', '09-agents', 'session-state.json');
|
|
1497
|
+
|
|
1498
|
+
/**
|
|
1499
|
+
* Switch active session context (for use with /add-dir).
|
|
1500
|
+
* Updates session-state.json with active_session info.
|
|
1501
|
+
*
|
|
1502
|
+
* @param {string} sessionIdOrNickname - Session ID or nickname to switch to
|
|
1503
|
+
* @returns {{ success: boolean, session?: object, path?: string, error?: string }}
|
|
1504
|
+
*/
|
|
1505
|
+
function switchSession(sessionIdOrNickname) {
|
|
1506
|
+
const registry = loadRegistry();
|
|
1507
|
+
|
|
1508
|
+
// Find session by ID or nickname
|
|
1509
|
+
let targetSession = null;
|
|
1510
|
+
let targetId = null;
|
|
1511
|
+
|
|
1512
|
+
for (const [id, session] of Object.entries(registry.sessions)) {
|
|
1513
|
+
if (id === sessionIdOrNickname || session.nickname === sessionIdOrNickname) {
|
|
1514
|
+
targetSession = session;
|
|
1515
|
+
targetId = id;
|
|
1516
|
+
break;
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
if (!targetSession) {
|
|
1521
|
+
return { success: false, error: `Session "${sessionIdOrNickname}" not found` };
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1524
|
+
// Verify the session path exists
|
|
1525
|
+
if (!fs.existsSync(targetSession.path)) {
|
|
1526
|
+
return {
|
|
1527
|
+
success: false,
|
|
1528
|
+
error: `Session directory does not exist: ${targetSession.path}`,
|
|
1529
|
+
};
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
// Load or create session-state.json
|
|
1533
|
+
let sessionState = {};
|
|
1534
|
+
if (fs.existsSync(SESSION_STATE_PATH)) {
|
|
1535
|
+
try {
|
|
1536
|
+
sessionState = JSON.parse(fs.readFileSync(SESSION_STATE_PATH, 'utf8'));
|
|
1537
|
+
} catch (e) {
|
|
1538
|
+
// Start fresh
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
// Update active_session
|
|
1543
|
+
sessionState.active_session = {
|
|
1544
|
+
id: targetId,
|
|
1545
|
+
nickname: targetSession.nickname,
|
|
1546
|
+
path: targetSession.path,
|
|
1547
|
+
branch: targetSession.branch,
|
|
1548
|
+
switched_at: new Date().toISOString(),
|
|
1549
|
+
original_cwd: ROOT,
|
|
1550
|
+
};
|
|
1551
|
+
|
|
1552
|
+
// Save session-state.json
|
|
1553
|
+
const stateDir = path.dirname(SESSION_STATE_PATH);
|
|
1554
|
+
if (!fs.existsSync(stateDir)) {
|
|
1555
|
+
fs.mkdirSync(stateDir, { recursive: true });
|
|
1556
|
+
}
|
|
1557
|
+
fs.writeFileSync(SESSION_STATE_PATH, JSON.stringify(sessionState, null, 2) + '\n');
|
|
1558
|
+
|
|
1559
|
+
// Update session last_active
|
|
1560
|
+
registry.sessions[targetId].last_active = new Date().toISOString();
|
|
1561
|
+
saveRegistry(registry);
|
|
1562
|
+
|
|
1563
|
+
return {
|
|
1564
|
+
success: true,
|
|
1565
|
+
session: {
|
|
1566
|
+
id: targetId,
|
|
1567
|
+
nickname: targetSession.nickname,
|
|
1568
|
+
path: targetSession.path,
|
|
1569
|
+
branch: targetSession.branch,
|
|
1570
|
+
},
|
|
1571
|
+
path: targetSession.path,
|
|
1572
|
+
addDirCommand: `/add-dir ${targetSession.path}`,
|
|
1573
|
+
};
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
/**
|
|
1577
|
+
* Clear active session (switch back to main/original).
|
|
1578
|
+
* @returns {{ success: boolean }}
|
|
1579
|
+
*/
|
|
1580
|
+
function clearActiveSession() {
|
|
1581
|
+
if (!fs.existsSync(SESSION_STATE_PATH)) {
|
|
1582
|
+
return { success: true };
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
try {
|
|
1586
|
+
const sessionState = JSON.parse(fs.readFileSync(SESSION_STATE_PATH, 'utf8'));
|
|
1587
|
+
delete sessionState.active_session;
|
|
1588
|
+
fs.writeFileSync(SESSION_STATE_PATH, JSON.stringify(sessionState, null, 2) + '\n');
|
|
1589
|
+
return { success: true };
|
|
1590
|
+
} catch (e) {
|
|
1591
|
+
return { success: false, error: e.message };
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
/**
|
|
1596
|
+
* Get current active session (if switched).
|
|
1597
|
+
* @returns {{ active: boolean, session?: object }}
|
|
1598
|
+
*/
|
|
1599
|
+
function getActiveSession() {
|
|
1600
|
+
if (!fs.existsSync(SESSION_STATE_PATH)) {
|
|
1601
|
+
return { active: false };
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
try {
|
|
1605
|
+
const sessionState = JSON.parse(fs.readFileSync(SESSION_STATE_PATH, 'utf8'));
|
|
1606
|
+
if (sessionState.active_session) {
|
|
1607
|
+
return { active: true, session: sessionState.active_session };
|
|
1608
|
+
}
|
|
1609
|
+
return { active: false };
|
|
1610
|
+
} catch (e) {
|
|
1611
|
+
return { active: false };
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1395
1615
|
// Export for use as module
|
|
1396
1616
|
module.exports = {
|
|
1397
1617
|
loadRegistry,
|
|
@@ -1415,6 +1635,10 @@ module.exports = {
|
|
|
1415
1635
|
categorizeFile,
|
|
1416
1636
|
getMergeStrategy,
|
|
1417
1637
|
getMergeHistory,
|
|
1638
|
+
// Session switching
|
|
1639
|
+
switchSession,
|
|
1640
|
+
clearActiveSession,
|
|
1641
|
+
getActiveSession,
|
|
1418
1642
|
};
|
|
1419
1643
|
|
|
1420
1644
|
// Run CLI if executed directly
|
|
@@ -133,26 +133,38 @@ Then create with specified branch:
|
|
|
133
133
|
node .agileflow/scripts/session-manager.js create --branch {branch_name}
|
|
134
134
|
```
|
|
135
135
|
|
|
136
|
-
### Step 5: Display Success
|
|
136
|
+
### Step 5: Display Success with Switch Command
|
|
137
137
|
|
|
138
|
-
|
|
138
|
+
After session creation succeeds:
|
|
139
139
|
|
|
140
|
+
1. First, activate boundary protection for the new session:
|
|
141
|
+
```bash
|
|
142
|
+
node .agileflow/scripts/session-manager.js switch {new_session_id}
|
|
140
143
|
```
|
|
141
|
-
✓ Created Session {id} "{nickname}"
|
|
142
144
|
|
|
143
|
-
|
|
144
|
-
Branch: session-{id}-{name}
|
|
145
|
+
2. Then show the `/add-dir` command for the user to switch:
|
|
145
146
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
│
|
|
151
|
-
|
|
147
|
+
```
|
|
148
|
+
✅ Created Session {id} "{nickname}"
|
|
149
|
+
|
|
150
|
+
┌───────────┬────────────────────────────────────────────┐
|
|
151
|
+
│ Session │ {id} "{nickname}" │
|
|
152
|
+
│ Workspace │ {path} │
|
|
153
|
+
│ Branch │ {branch} │
|
|
154
|
+
└───────────┴────────────────────────────────────────────┘
|
|
155
|
+
|
|
156
|
+
To switch to this session, run:
|
|
157
|
+
|
|
158
|
+
/add-dir {path}
|
|
152
159
|
|
|
153
|
-
💡
|
|
160
|
+
💡 Use /agileflow:session:resume to list all sessions
|
|
154
161
|
```
|
|
155
162
|
|
|
163
|
+
**WHY /add-dir instead of cd && claude:**
|
|
164
|
+
- Stays in the same terminal and conversation
|
|
165
|
+
- One short command to type
|
|
166
|
+
- Immediately enables file access to the new session directory
|
|
167
|
+
|
|
156
168
|
## Error Handling
|
|
157
169
|
|
|
158
170
|
- **Directory exists**: Suggest different name or manual cleanup
|
|
@@ -228,21 +240,26 @@ If user selects "Auto-create":
|
|
|
228
240
|
node .agileflow/scripts/session-manager.js create
|
|
229
241
|
```
|
|
230
242
|
|
|
231
|
-
|
|
243
|
+
Parse JSON result, then activate boundary protection:
|
|
244
|
+
```bash
|
|
245
|
+
node .agileflow/scripts/session-manager.js switch {new_id}
|
|
232
246
|
```
|
|
233
|
-
✓ Created Session {id}
|
|
234
247
|
|
|
235
|
-
|
|
236
|
-
|
|
248
|
+
Then display:
|
|
249
|
+
```
|
|
250
|
+
✅ Created Session {id}
|
|
237
251
|
|
|
238
|
-
|
|
239
|
-
│
|
|
240
|
-
│
|
|
241
|
-
│
|
|
242
|
-
|
|
243
|
-
|
|
252
|
+
┌───────────┬────────────────────────────────────────────┐
|
|
253
|
+
│ Session │ {id} │
|
|
254
|
+
│ Workspace │ {path} │
|
|
255
|
+
│ Branch │ {branch} │
|
|
256
|
+
└───────────┴────────────────────────────────────────────┘
|
|
257
|
+
|
|
258
|
+
To switch to this session, run:
|
|
244
259
|
|
|
245
|
-
|
|
260
|
+
/add-dir {path}
|
|
261
|
+
|
|
262
|
+
💡 Use /agileflow:session:resume to list all sessions
|
|
246
263
|
```
|
|
247
264
|
|
|
248
265
|
---
|
|
@@ -274,21 +291,26 @@ Then create:
|
|
|
274
291
|
node .agileflow/scripts/session-manager.js create --nickname {name}
|
|
275
292
|
```
|
|
276
293
|
|
|
277
|
-
|
|
294
|
+
Parse JSON result, then activate boundary protection:
|
|
295
|
+
```bash
|
|
296
|
+
node .agileflow/scripts/session-manager.js switch {new_id}
|
|
278
297
|
```
|
|
279
|
-
✓ Created Session {id} "{name}"
|
|
280
298
|
|
|
281
|
-
|
|
282
|
-
|
|
299
|
+
Then display:
|
|
300
|
+
```
|
|
301
|
+
✅ Created Session {id} "{name}"
|
|
283
302
|
|
|
284
|
-
|
|
285
|
-
│
|
|
286
|
-
│
|
|
287
|
-
│
|
|
288
|
-
|
|
289
|
-
|
|
303
|
+
┌───────────┬────────────────────────────────────────────┐
|
|
304
|
+
│ Session │ {id} "{name}" │
|
|
305
|
+
│ Workspace │ {path} │
|
|
306
|
+
│ Branch │ {branch} │
|
|
307
|
+
└───────────┴────────────────────────────────────────────┘
|
|
308
|
+
|
|
309
|
+
To switch to this session, run:
|
|
310
|
+
|
|
311
|
+
/add-dir {path}
|
|
290
312
|
|
|
291
|
-
💡
|
|
313
|
+
💡 Use /agileflow:session:resume to list all sessions
|
|
292
314
|
```
|
|
293
315
|
|
|
294
316
|
---
|
|
@@ -327,7 +349,12 @@ git branch --format='%(refname:short)'
|
|
|
327
349
|
node .agileflow/scripts/session-manager.js create --branch {branch_name}
|
|
328
350
|
```
|
|
329
351
|
|
|
330
|
-
|
|
352
|
+
5. Parse JSON result, then activate boundary protection:
|
|
353
|
+
```bash
|
|
354
|
+
node .agileflow/scripts/session-manager.js switch {new_id}
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
6. Display success as above with `/add-dir` command.
|
|
331
358
|
|
|
332
359
|
---
|
|
333
360
|
|
|
@@ -365,21 +392,23 @@ Try running: git status
|
|
|
365
392
|
|
|
366
393
|
All three options show same format:
|
|
367
394
|
```
|
|
368
|
-
|
|
395
|
+
✅ Created Session {id} ["{nickname}" OR empty]
|
|
369
396
|
|
|
370
|
-
|
|
371
|
-
|
|
397
|
+
┌───────────┬────────────────────────────────────────────┐
|
|
398
|
+
│ Session │ {id} ["{nickname}" or empty] │
|
|
399
|
+
│ Workspace │ {path} │
|
|
400
|
+
│ Branch │ {branch} │
|
|
401
|
+
└───────────┴────────────────────────────────────────────┘
|
|
372
402
|
|
|
373
|
-
|
|
374
|
-
│ To start working in this session, run: │
|
|
375
|
-
│ │
|
|
376
|
-
│ {cd_command} │
|
|
377
|
-
│ │
|
|
378
|
-
└─────────────────────────────────────────────────────────┘
|
|
403
|
+
To switch to this session, run:
|
|
379
404
|
|
|
380
|
-
|
|
405
|
+
/add-dir {path}
|
|
406
|
+
|
|
407
|
+
💡 Use /agileflow:session:resume to list all sessions
|
|
381
408
|
```
|
|
382
409
|
|
|
410
|
+
**Use /add-dir instead of cd && claude** - stays in same terminal/conversation.
|
|
411
|
+
|
|
383
412
|
---
|
|
384
413
|
|
|
385
414
|
### KEY FILES TO REMEMBER
|
|
@@ -400,7 +429,8 @@ All three options show same format:
|
|
|
400
429
|
4. **User selects** → Option 1, 2, or 3
|
|
401
430
|
5. **Handle selection** → Different flow for each
|
|
402
431
|
6. **Create session** → Call manager script
|
|
403
|
-
7. **
|
|
432
|
+
7. **Activate boundary** → `session-manager.js switch {new_id}`
|
|
433
|
+
8. **Show success** → Display `/add-dir {path}` command for user to run
|
|
404
434
|
|
|
405
435
|
---
|
|
406
436
|
|
|
@@ -420,7 +450,7 @@ All three options show same format:
|
|
|
420
450
|
❌ Don't show more/fewer than 3 initial options
|
|
421
451
|
❌ Don't create session without explicit user choice
|
|
422
452
|
❌ Don't skip error handling (directory exists, branch conflict)
|
|
423
|
-
❌ Don't
|
|
453
|
+
❌ Don't show old "cd && claude" command - use /add-dir instead
|
|
424
454
|
❌ Show different success formats for different methods
|
|
425
455
|
|
|
426
456
|
### DO THESE INSTEAD
|
|
@@ -429,7 +459,7 @@ All three options show same format:
|
|
|
429
459
|
✅ Always show exactly 3 options
|
|
430
460
|
✅ Wait for user to select before creating
|
|
431
461
|
✅ Handle all error cases gracefully
|
|
432
|
-
✅
|
|
462
|
+
✅ Show `/add-dir {path}` command for user to switch
|
|
433
463
|
✅ Use consistent success format
|
|
434
464
|
|
|
435
465
|
---
|
|
@@ -442,7 +472,8 @@ All three options show same format:
|
|
|
442
472
|
- Each option leads to different flow
|
|
443
473
|
- Use AskUserQuestion for user selections
|
|
444
474
|
- Handle all error cases (directory, branch, git)
|
|
445
|
-
-
|
|
475
|
+
- **Run `session-manager.js switch {new_id}` AFTER creating session** (enables boundary protection)
|
|
476
|
+
- Show `/add-dir {path}` command for user to switch (NOT cd && claude)
|
|
446
477
|
- Show tip to use /agileflow:session:resume
|
|
447
478
|
|
|
448
479
|
<!-- COMPACT_SUMMARY_END -->
|