agileflow 2.98.1 → 2.99.1
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/README.md +3 -3
- package/lib/api-routes.js +28 -8
- package/lib/api-server.js +21 -4
- package/lib/dashboard-protocol.js +38 -0
- package/lib/dashboard-server.js +355 -17
- package/lib/feedback.js +35 -9
- package/lib/git-operations.js +4 -1
- package/lib/merge-operations.js +16 -0
- package/lib/progress.js +7 -6
- package/lib/session-operations.js +601 -0
- package/lib/session-switching.js +186 -0
- package/lib/template-loader.js +4 -2
- package/lib/worktree-operations.js +5 -25
- package/package.json +1 -1
- package/scripts/agileflow-configure.js +12 -0
- package/scripts/batch-pmap-loop.js +11 -4
- package/scripts/claude-tmux.sh +186 -103
- package/scripts/lib/configure-features.js +6 -4
- package/scripts/lib/configure-repair.js +11 -2
- package/scripts/lib/damage-control-utils.js +38 -0
- package/scripts/lib/process-cleanup.js +9 -5
- package/scripts/obtain-context.js +5 -0
- package/scripts/session-manager.js +144 -993
- package/scripts/spawn-parallel.js +15 -11
- package/src/core/commands/configure.md +55 -0
- package/src/core/commands/serve.md +127 -0
- package/src/core/commands/session/end.md +83 -22
- package/src/core/commands/session/new.md +197 -83
- package/src/core/templates/damage-control-patterns.yaml +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [2.99.1] - 2026-02-08
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- Smart session management with context-aware naming and tmux quick-create keybinds
|
|
14
|
+
|
|
15
|
+
## [2.99.0] - 2026-02-08
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
- Security Hardening Phase 2 - path traversal, auth, CORS, ReDoS, rate limiting
|
|
19
|
+
|
|
10
20
|
## [2.98.1] - 2026-02-07
|
|
11
21
|
|
|
12
22
|
### Added
|
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/agileflow)
|
|
6
|
-
[](docs/04-architecture/commands.md)
|
|
7
7
|
[](docs/04-architecture/subagents.md)
|
|
8
8
|
[](docs/04-architecture/skills.md)
|
|
9
9
|
|
|
@@ -65,7 +65,7 @@ AgileFlow combines three proven methodologies:
|
|
|
65
65
|
|
|
66
66
|
| Component | Count | Description |
|
|
67
67
|
|-----------|-------|-------------|
|
|
68
|
-
| [Commands](docs/04-architecture/commands.md) |
|
|
68
|
+
| [Commands](docs/04-architecture/commands.md) | 91 | Slash commands for agile workflows |
|
|
69
69
|
| [Agents/Experts](docs/04-architecture/subagents.md) | 47 | 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
|
|
|
@@ -76,7 +76,7 @@ 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
|
|
79
|
+
- [Commands](docs/04-architecture/commands.md) - All 91 slash commands
|
|
80
80
|
- [Agents/Experts](docs/04-architecture/subagents.md) - 47 specialized agents with self-improving knowledge
|
|
81
81
|
- [Skills](docs/04-architecture/skills.md) - Dynamic skill generator with MCP integration
|
|
82
82
|
|
package/lib/api-routes.js
CHANGED
|
@@ -26,6 +26,10 @@ const {
|
|
|
26
26
|
} = require('./paths');
|
|
27
27
|
const { SessionRegistry } = require('./session-registry');
|
|
28
28
|
const { getTaskRegistry } = require('../scripts/lib/task-registry');
|
|
29
|
+
const { validatePath } = require('./validate-paths');
|
|
30
|
+
|
|
31
|
+
// Allow-list regex for resource IDs (epics, stories, tasks, sessions)
|
|
32
|
+
const SAFE_ID_PATTERN = /^[A-Za-z0-9_-]+$/;
|
|
29
33
|
|
|
30
34
|
/**
|
|
31
35
|
* Get API route handlers
|
|
@@ -139,6 +143,10 @@ async function getSessions(sessionRegistry, cache) {
|
|
|
139
143
|
* GET /api/sessions/:id - Get session by ID
|
|
140
144
|
*/
|
|
141
145
|
async function getSessionById(sessionRegistry, id, cache) {
|
|
146
|
+
if (!SAFE_ID_PATTERN.test(id)) {
|
|
147
|
+
return { error: 'Invalid session ID format' };
|
|
148
|
+
}
|
|
149
|
+
|
|
142
150
|
const cacheKey = `session-${id}`;
|
|
143
151
|
const cached = cache.get(cacheKey);
|
|
144
152
|
if (cached) return cached;
|
|
@@ -183,7 +191,7 @@ function getStatus(rootDir, cache) {
|
|
|
183
191
|
cache.set(cacheKey, result);
|
|
184
192
|
return result;
|
|
185
193
|
} catch (error) {
|
|
186
|
-
return { error: 'Failed to parse status file'
|
|
194
|
+
return { error: 'Failed to parse status file' };
|
|
187
195
|
}
|
|
188
196
|
}
|
|
189
197
|
|
|
@@ -226,7 +234,7 @@ function getTasks(rootDir, queryParams, cache) {
|
|
|
226
234
|
cache.set(cacheKey, result);
|
|
227
235
|
return result;
|
|
228
236
|
} catch (error) {
|
|
229
|
-
return { error: 'Failed to load tasks'
|
|
237
|
+
return { error: 'Failed to load tasks' };
|
|
230
238
|
}
|
|
231
239
|
}
|
|
232
240
|
|
|
@@ -234,6 +242,10 @@ function getTasks(rootDir, queryParams, cache) {
|
|
|
234
242
|
* GET /api/tasks/:id - Get task by ID
|
|
235
243
|
*/
|
|
236
244
|
function getTaskById(rootDir, id, cache) {
|
|
245
|
+
if (!SAFE_ID_PATTERN.test(id)) {
|
|
246
|
+
return { error: 'Invalid task ID format' };
|
|
247
|
+
}
|
|
248
|
+
|
|
237
249
|
const cacheKey = `task-${id}`;
|
|
238
250
|
const cached = cache.get(cacheKey);
|
|
239
251
|
if (cached) return cached;
|
|
@@ -249,7 +261,7 @@ function getTaskById(rootDir, id, cache) {
|
|
|
249
261
|
cache.set(cacheKey, task);
|
|
250
262
|
return task;
|
|
251
263
|
} catch (error) {
|
|
252
|
-
return { error: 'Failed to load task'
|
|
264
|
+
return { error: 'Failed to load task' };
|
|
253
265
|
}
|
|
254
266
|
}
|
|
255
267
|
|
|
@@ -300,7 +312,7 @@ async function getBusMessages(rootDir, queryParams, cache) {
|
|
|
300
312
|
cache.set(cacheKey, result);
|
|
301
313
|
return result;
|
|
302
314
|
} catch (error) {
|
|
303
|
-
return { error: 'Failed to read bus log'
|
|
315
|
+
return { error: 'Failed to read bus log' };
|
|
304
316
|
}
|
|
305
317
|
}
|
|
306
318
|
|
|
@@ -448,7 +460,7 @@ function getEpics(rootDir, cache) {
|
|
|
448
460
|
cache.set(cacheKey, result);
|
|
449
461
|
return result;
|
|
450
462
|
} catch (error) {
|
|
451
|
-
return { error: 'Failed to list epics'
|
|
463
|
+
return { error: 'Failed to list epics' };
|
|
452
464
|
}
|
|
453
465
|
}
|
|
454
466
|
|
|
@@ -456,6 +468,10 @@ function getEpics(rootDir, cache) {
|
|
|
456
468
|
* GET /api/epics/:id - Get epic by ID
|
|
457
469
|
*/
|
|
458
470
|
function getEpicById(rootDir, id, cache) {
|
|
471
|
+
if (!SAFE_ID_PATTERN.test(id)) {
|
|
472
|
+
return { error: 'Invalid epic ID format' };
|
|
473
|
+
}
|
|
474
|
+
|
|
459
475
|
const cacheKey = `epic-${id}`;
|
|
460
476
|
const cached = cache.get(cacheKey);
|
|
461
477
|
if (cached) return cached;
|
|
@@ -479,7 +495,7 @@ function getEpicById(rootDir, id, cache) {
|
|
|
479
495
|
cache.set(cacheKey, result);
|
|
480
496
|
return result;
|
|
481
497
|
} catch (error) {
|
|
482
|
-
return { error: 'Failed to read epic'
|
|
498
|
+
return { error: 'Failed to read epic' };
|
|
483
499
|
}
|
|
484
500
|
}
|
|
485
501
|
|
|
@@ -533,7 +549,7 @@ function getStories(rootDir, queryParams, cache) {
|
|
|
533
549
|
|
|
534
550
|
return { stories: [], count: 0, timestamp: new Date().toISOString() };
|
|
535
551
|
} catch (error) {
|
|
536
|
-
return { error: 'Failed to list stories'
|
|
552
|
+
return { error: 'Failed to list stories' };
|
|
537
553
|
}
|
|
538
554
|
}
|
|
539
555
|
|
|
@@ -541,6 +557,10 @@ function getStories(rootDir, queryParams, cache) {
|
|
|
541
557
|
* GET /api/stories/:id - Get story by ID
|
|
542
558
|
*/
|
|
543
559
|
function getStoryById(rootDir, id, cache) {
|
|
560
|
+
if (!SAFE_ID_PATTERN.test(id)) {
|
|
561
|
+
return { error: 'Invalid story ID format' };
|
|
562
|
+
}
|
|
563
|
+
|
|
544
564
|
const cacheKey = `story-${id}`;
|
|
545
565
|
const cached = cache.get(cacheKey);
|
|
546
566
|
if (cached) return cached;
|
|
@@ -569,7 +589,7 @@ function getStoryById(rootDir, id, cache) {
|
|
|
569
589
|
|
|
570
590
|
return { error: 'Story not found', id };
|
|
571
591
|
} catch (error) {
|
|
572
|
-
return { error: 'Failed to read story'
|
|
592
|
+
return { error: 'Failed to read story' };
|
|
573
593
|
}
|
|
574
594
|
}
|
|
575
595
|
|
package/lib/api-server.js
CHANGED
|
@@ -87,13 +87,31 @@ function createApiServer(options = {}) {
|
|
|
87
87
|
// Get route handlers
|
|
88
88
|
const routes = getApiRoutes(rootDir, cache);
|
|
89
89
|
|
|
90
|
+
// Localhost CORS allowlist
|
|
91
|
+
const ALLOWED_ORIGINS = [
|
|
92
|
+
`http://localhost:${port}`,
|
|
93
|
+
`http://127.0.0.1:${port}`,
|
|
94
|
+
'http://localhost:3000',
|
|
95
|
+
'http://127.0.0.1:3000',
|
|
96
|
+
'http://localhost:5173',
|
|
97
|
+
'http://127.0.0.1:5173',
|
|
98
|
+
];
|
|
99
|
+
|
|
90
100
|
// Create HTTP server
|
|
91
101
|
const server = http.createServer(async (req, res) => {
|
|
92
|
-
//
|
|
93
|
-
res.setHeader('
|
|
102
|
+
// Security headers
|
|
103
|
+
res.setHeader('X-Content-Type-Options', 'nosniff');
|
|
104
|
+
res.setHeader('X-Frame-Options', 'DENY');
|
|
105
|
+
res.setHeader('Cache-Control', 'no-store');
|
|
106
|
+
res.setHeader('Content-Type', 'application/json');
|
|
107
|
+
|
|
108
|
+
// CORS - restrict to localhost origins
|
|
109
|
+
const origin = req.headers.origin;
|
|
110
|
+
if (origin && ALLOWED_ORIGINS.some(allowed => origin === allowed)) {
|
|
111
|
+
res.setHeader('Access-Control-Allow-Origin', origin);
|
|
112
|
+
}
|
|
94
113
|
res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
|
|
95
114
|
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
|
96
|
-
res.setHeader('Content-Type', 'application/json');
|
|
97
115
|
|
|
98
116
|
// Handle preflight
|
|
99
117
|
if (req.method === 'OPTIONS') {
|
|
@@ -155,7 +173,6 @@ function createApiServer(options = {}) {
|
|
|
155
173
|
res.end(
|
|
156
174
|
JSON.stringify({
|
|
157
175
|
error: 'Internal server error',
|
|
158
|
-
message: error.message,
|
|
159
176
|
})
|
|
160
177
|
);
|
|
161
178
|
}
|
|
@@ -78,6 +78,9 @@ const OutboundMessageType = {
|
|
|
78
78
|
// User Interaction
|
|
79
79
|
ASK_USER_QUESTION: 'ask_user_question', // Claude is asking user a question
|
|
80
80
|
|
|
81
|
+
// Sessions
|
|
82
|
+
SESSION_LIST: 'session_list', // Session list with sync status
|
|
83
|
+
|
|
81
84
|
// Errors
|
|
82
85
|
ERROR: 'error', // General error
|
|
83
86
|
};
|
|
@@ -123,6 +126,9 @@ const InboundMessageType = {
|
|
|
123
126
|
INBOX_LIST_REQUEST: 'inbox_list_request', // Request inbox list
|
|
124
127
|
INBOX_ACTION: 'inbox_action', // Accept/dismiss inbox item
|
|
125
128
|
|
|
129
|
+
// File Operations
|
|
130
|
+
OPEN_FILE: 'open_file', // Open file in editor
|
|
131
|
+
|
|
126
132
|
// User Interaction Response
|
|
127
133
|
USER_ANSWER: 'user_answer', // User's answer to AskUserQuestion
|
|
128
134
|
};
|
|
@@ -435,6 +441,36 @@ function createInboxItem(item) {
|
|
|
435
441
|
};
|
|
436
442
|
}
|
|
437
443
|
|
|
444
|
+
/**
|
|
445
|
+
* Create a project status update message
|
|
446
|
+
* @param {Object} summary - Status summary
|
|
447
|
+
* @param {number} summary.total - Total stories
|
|
448
|
+
* @param {number} summary.done - Completed stories
|
|
449
|
+
* @param {number} summary.inProgress - In-progress stories
|
|
450
|
+
* @param {number} summary.ready - Ready stories
|
|
451
|
+
* @param {number} summary.blocked - Blocked stories
|
|
452
|
+
* @param {Object[]} summary.epics - Epic summaries
|
|
453
|
+
*/
|
|
454
|
+
function createStatusUpdate(summary) {
|
|
455
|
+
return {
|
|
456
|
+
type: OutboundMessageType.STATUS_UPDATE,
|
|
457
|
+
...summary,
|
|
458
|
+
timestamp: new Date().toISOString(),
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Create a session list message with sync status
|
|
464
|
+
* @param {Object[]} sessions - Array of session objects with sync info
|
|
465
|
+
*/
|
|
466
|
+
function createSessionList(sessions) {
|
|
467
|
+
return {
|
|
468
|
+
type: OutboundMessageType.SESSION_LIST,
|
|
469
|
+
sessions,
|
|
470
|
+
timestamp: new Date().toISOString(),
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
|
|
438
474
|
/**
|
|
439
475
|
* Create an AskUserQuestion message
|
|
440
476
|
* @param {string} toolId - Tool call ID for response correlation
|
|
@@ -533,6 +569,8 @@ module.exports = {
|
|
|
533
569
|
createInboxList,
|
|
534
570
|
createInboxItem,
|
|
535
571
|
createAskUserQuestion,
|
|
572
|
+
createStatusUpdate,
|
|
573
|
+
createSessionList,
|
|
536
574
|
|
|
537
575
|
// Parsing
|
|
538
576
|
parseInboundMessage,
|