agileflow 3.0.0 → 3.0.2
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 +6 -6
- package/lib/api-server.js +3 -2
- package/lib/dashboard-server.js +131 -50
- package/lib/flag-detection.js +4 -2
- package/lib/git-operations.js +4 -2
- package/lib/process-executor.js +24 -9
- package/lib/skill-loader.js +11 -3
- package/package.json +1 -1
- package/scripts/agileflow-welcome.js +65 -25
- package/scripts/archive-completed-stories.sh +3 -0
- package/scripts/ci-summary.js +294 -0
- package/scripts/claude-smart.sh +18 -0
- package/scripts/claude-tmux.sh +50 -20
- package/scripts/damage-control-multi-agent.js +14 -10
- package/scripts/lib/bus-utils.js +3 -1
- package/scripts/lib/configure-detect.js +89 -8
- package/scripts/lib/configure-features.js +77 -10
- package/scripts/lib/configure-repair.js +6 -5
- package/scripts/lib/context-formatter.js +13 -3
- package/scripts/lib/damage-control-utils.js +5 -1
- package/scripts/lib/lifecycle-detector.js +5 -3
- package/scripts/lib/process-cleanup.js +8 -4
- package/scripts/lib/scale-detector.js +47 -8
- package/scripts/lib/signal-detectors.js +117 -59
- package/scripts/lib/task-registry.js +5 -1
- package/scripts/lib/team-events.js +4 -4
- package/scripts/messaging-bridge.js +7 -1
- package/scripts/precompact-context.sh +3 -0
- package/scripts/ralph-loop.js +10 -8
- package/scripts/smart-detect.js +32 -11
- package/scripts/team-manager.js +1 -1
- package/scripts/tmux-task-name.sh +75 -0
- package/scripts/tmux-task-watcher.sh +177 -0
- package/src/core/commands/babysit.md +75 -42
- package/src/core/commands/blockers.md +7 -7
- package/src/core/commands/configure.md +49 -63
- package/src/core/commands/discovery/brief.md +363 -0
- package/src/core/commands/discovery/new.md +395 -0
- package/src/core/commands/ideate/new.md +5 -5
- package/src/core/commands/logic/audit.md +5 -5
- package/src/core/commands/review.md +7 -1
- package/src/core/commands/rpi.md +61 -26
- package/src/core/commands/sprint.md +7 -6
- package/src/core/templates/product-brief.md +136 -0
- package/tools/cli/installers/ide/claude-code.js +67 -2
- package/src/core/agents/configuration/archival.md +0 -350
- package/src/core/agents/configuration/attribution.md +0 -343
- package/src/core/agents/configuration/ci.md +0 -1103
- package/src/core/agents/configuration/damage-control.md +0 -375
- package/src/core/agents/configuration/git-config.md +0 -537
- package/src/core/agents/configuration/hooks.md +0 -623
- package/src/core/agents/configuration/precompact.md +0 -302
- package/src/core/agents/configuration/status-line.md +0 -557
- package/src/core/agents/configuration/verify.md +0 -618
- package/src/core/agents/configuration-damage-control.md +0 -259
- package/src/core/agents/configuration-visual-e2e.md +0 -339
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
|
+
## [3.0.2] - 2026-02-14
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- Automatic tmux window naming and configuration agent consolidation
|
|
14
|
+
|
|
15
|
+
## [3.0.1] - 2026-02-13
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
- Configure writes directly to Claude Code settings.json for Agent Teams and permissions
|
|
19
|
+
|
|
10
20
|
## [3.0.0] - 2026-02-13
|
|
11
21
|
|
|
12
22
|
### Added
|
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) | 93 | Slash commands for agile workflows |
|
|
69
|
+
| [Agents/Experts](docs/04-architecture/subagents.md) | 45 | 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 93 slash commands
|
|
80
|
+
- [Agents/Experts](docs/04-architecture/subagents.md) - 45 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/lib/api-server.js
CHANGED
|
@@ -71,7 +71,7 @@ class ApiCache {
|
|
|
71
71
|
* @returns {{ server: http.Server, options: Object, cache: ApiCache }}
|
|
72
72
|
*/
|
|
73
73
|
function createApiServer(options = {}) {
|
|
74
|
-
const port = options.port
|
|
74
|
+
const port = options.port != null ? options.port : DEFAULT_PORT;
|
|
75
75
|
const host = options.host || DEFAULT_HOST;
|
|
76
76
|
const rootDir = options.rootDir || getProjectRoot();
|
|
77
77
|
const cacheTTL = options.cacheTTL || 2000;
|
|
@@ -238,7 +238,8 @@ function startApiServer(serverInstance) {
|
|
|
238
238
|
});
|
|
239
239
|
|
|
240
240
|
server.listen(port, host, () => {
|
|
241
|
-
const
|
|
241
|
+
const actualPort = server.address().port;
|
|
242
|
+
const url = `http://${host}:${actualPort}`;
|
|
242
243
|
console.log(`[AgileFlow API] Server running at ${url}`);
|
|
243
244
|
console.log(`[AgileFlow API] Project root: ${options.rootDir}`);
|
|
244
245
|
resolve({
|
package/lib/dashboard-server.js
CHANGED
|
@@ -26,12 +26,30 @@ const { EventEmitter } = require('events');
|
|
|
26
26
|
// Lazy-loaded dependencies - deferred until first use
|
|
27
27
|
let _http, _crypto, _protocol, _paths, _validatePaths, _childProcess;
|
|
28
28
|
|
|
29
|
-
function getHttp() {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
function
|
|
34
|
-
|
|
29
|
+
function getHttp() {
|
|
30
|
+
if (!_http) _http = require('http');
|
|
31
|
+
return _http;
|
|
32
|
+
}
|
|
33
|
+
function getCrypto() {
|
|
34
|
+
if (!_crypto) _crypto = require('crypto');
|
|
35
|
+
return _crypto;
|
|
36
|
+
}
|
|
37
|
+
function getProtocol() {
|
|
38
|
+
if (!_protocol) _protocol = require('./dashboard-protocol');
|
|
39
|
+
return _protocol;
|
|
40
|
+
}
|
|
41
|
+
function getPaths() {
|
|
42
|
+
if (!_paths) _paths = require('./paths');
|
|
43
|
+
return _paths;
|
|
44
|
+
}
|
|
45
|
+
function getValidatePaths() {
|
|
46
|
+
if (!_validatePaths) _validatePaths = require('./validate-paths');
|
|
47
|
+
return _validatePaths;
|
|
48
|
+
}
|
|
49
|
+
function getChildProcess() {
|
|
50
|
+
if (!_childProcess) _childProcess = require('child_process');
|
|
51
|
+
return _childProcess;
|
|
52
|
+
}
|
|
35
53
|
|
|
36
54
|
// Lazy-load automation modules to avoid circular dependencies
|
|
37
55
|
let AutomationRegistry = null;
|
|
@@ -294,7 +312,9 @@ class TerminalInstance {
|
|
|
294
312
|
this.pty.on('error', error => {
|
|
295
313
|
console.error('[Terminal] Shell error:', error.message);
|
|
296
314
|
if (!this.closed) {
|
|
297
|
-
this.session.send(
|
|
315
|
+
this.session.send(
|
|
316
|
+
getProtocol().createTerminalOutput(this.id, `\r\nError: ${error.message}\r\n`)
|
|
317
|
+
);
|
|
298
318
|
}
|
|
299
319
|
});
|
|
300
320
|
|
|
@@ -558,7 +578,9 @@ class DashboardServer extends EventEmitter {
|
|
|
558
578
|
|
|
559
579
|
this._automationRunner.on('failed', ({ automationId, result }) => {
|
|
560
580
|
this._runningAutomations.delete(automationId);
|
|
561
|
-
this.broadcast(
|
|
581
|
+
this.broadcast(
|
|
582
|
+
getProtocol().createAutomationStatus(automationId, 'error', { error: result.error })
|
|
583
|
+
);
|
|
562
584
|
|
|
563
585
|
// Add failure to inbox
|
|
564
586
|
this._addToInbox(automationId, result);
|
|
@@ -732,7 +754,7 @@ class DashboardServer extends EventEmitter {
|
|
|
732
754
|
|
|
733
755
|
// Complete WebSocket handshake
|
|
734
756
|
const key = req.headers['sec-websocket-key'];
|
|
735
|
-
const acceptKey =
|
|
757
|
+
const acceptKey = getCrypto()
|
|
736
758
|
.createHash('sha1')
|
|
737
759
|
.update(key + WS_GUID)
|
|
738
760
|
.digest('base64');
|
|
@@ -865,7 +887,9 @@ class DashboardServer extends EventEmitter {
|
|
|
865
887
|
handleMessage(session, data) {
|
|
866
888
|
// Rate limit incoming messages
|
|
867
889
|
if (!session.checkRateLimit()) {
|
|
868
|
-
session.send(
|
|
890
|
+
session.send(
|
|
891
|
+
getProtocol().createError('RATE_LIMITED', 'Too many messages, please slow down')
|
|
892
|
+
);
|
|
869
893
|
return;
|
|
870
894
|
}
|
|
871
895
|
|
|
@@ -1059,7 +1083,9 @@ class DashboardServer extends EventEmitter {
|
|
|
1059
1083
|
switch (type) {
|
|
1060
1084
|
case getProtocol().InboundMessageType.GIT_STAGE:
|
|
1061
1085
|
if (fileArgs) {
|
|
1062
|
-
getChildProcess().execFileSync('git', ['add', '--', ...fileArgs], {
|
|
1086
|
+
getChildProcess().execFileSync('git', ['add', '--', ...fileArgs], {
|
|
1087
|
+
cwd: this.projectRoot,
|
|
1088
|
+
});
|
|
1063
1089
|
} else {
|
|
1064
1090
|
getChildProcess().execFileSync('git', ['add', '-A'], { cwd: this.projectRoot });
|
|
1065
1091
|
}
|
|
@@ -1070,24 +1096,32 @@ class DashboardServer extends EventEmitter {
|
|
|
1070
1096
|
cwd: this.projectRoot,
|
|
1071
1097
|
});
|
|
1072
1098
|
} else {
|
|
1073
|
-
getChildProcess().execFileSync('git', ['restore', '--staged', '.'], {
|
|
1099
|
+
getChildProcess().execFileSync('git', ['restore', '--staged', '.'], {
|
|
1100
|
+
cwd: this.projectRoot,
|
|
1101
|
+
});
|
|
1074
1102
|
}
|
|
1075
1103
|
break;
|
|
1076
1104
|
case getProtocol().InboundMessageType.GIT_REVERT:
|
|
1077
1105
|
if (fileArgs) {
|
|
1078
|
-
getChildProcess().execFileSync('git', ['checkout', '--', ...fileArgs], {
|
|
1106
|
+
getChildProcess().execFileSync('git', ['checkout', '--', ...fileArgs], {
|
|
1107
|
+
cwd: this.projectRoot,
|
|
1108
|
+
});
|
|
1079
1109
|
}
|
|
1080
1110
|
break;
|
|
1081
1111
|
case getProtocol().InboundMessageType.GIT_COMMIT:
|
|
1082
1112
|
if (commitMessage) {
|
|
1083
|
-
getChildProcess().execFileSync('git', ['commit', '-m', commitMessage], {
|
|
1113
|
+
getChildProcess().execFileSync('git', ['commit', '-m', commitMessage], {
|
|
1114
|
+
cwd: this.projectRoot,
|
|
1115
|
+
});
|
|
1084
1116
|
}
|
|
1085
1117
|
break;
|
|
1086
1118
|
}
|
|
1087
1119
|
|
|
1088
1120
|
// Send updated git status
|
|
1089
1121
|
this.sendGitStatus(session);
|
|
1090
|
-
session.send(
|
|
1122
|
+
session.send(
|
|
1123
|
+
getProtocol().createNotification('success', 'Git', `${type.replace('git_', '')} completed`)
|
|
1124
|
+
);
|
|
1091
1125
|
} catch (error) {
|
|
1092
1126
|
console.error('[Git Error]', error.message);
|
|
1093
1127
|
session.send(getProtocol().createError('GIT_ERROR', 'Git operation failed'));
|
|
@@ -1115,11 +1149,13 @@ class DashboardServer extends EventEmitter {
|
|
|
1115
1149
|
*/
|
|
1116
1150
|
getGitStatus() {
|
|
1117
1151
|
try {
|
|
1118
|
-
const branch = getChildProcess()
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1152
|
+
const branch = getChildProcess()
|
|
1153
|
+
.execFileSync('git', ['branch', '--show-current'], {
|
|
1154
|
+
cwd: this.projectRoot,
|
|
1155
|
+
encoding: 'utf8',
|
|
1156
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
1157
|
+
})
|
|
1158
|
+
.trim();
|
|
1123
1159
|
|
|
1124
1160
|
const statusOutput = getChildProcess().execFileSync('git', ['status', '--porcelain'], {
|
|
1125
1161
|
cwd: this.projectRoot,
|
|
@@ -1209,7 +1245,9 @@ class DashboardServer extends EventEmitter {
|
|
|
1209
1245
|
*/
|
|
1210
1246
|
getFileDiff(filePath, staged = false) {
|
|
1211
1247
|
// Validate filePath stays within project root
|
|
1212
|
-
const pathResult = getValidatePaths().validatePath(filePath, this.projectRoot, {
|
|
1248
|
+
const pathResult = getValidatePaths().validatePath(filePath, this.projectRoot, {
|
|
1249
|
+
allowSymlinks: true,
|
|
1250
|
+
});
|
|
1213
1251
|
if (!pathResult.ok) {
|
|
1214
1252
|
return '';
|
|
1215
1253
|
}
|
|
@@ -1224,10 +1262,12 @@ class DashboardServer extends EventEmitter {
|
|
|
1224
1262
|
|
|
1225
1263
|
// If no diff, file might be untracked - show entire file content as addition
|
|
1226
1264
|
if (!diff && !staged) {
|
|
1227
|
-
const statusOutput = getChildProcess()
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1265
|
+
const statusOutput = getChildProcess()
|
|
1266
|
+
.execFileSync('git', ['status', '--porcelain', '--', filePath], {
|
|
1267
|
+
cwd: this.projectRoot,
|
|
1268
|
+
encoding: 'utf8',
|
|
1269
|
+
})
|
|
1270
|
+
.trim();
|
|
1231
1271
|
|
|
1232
1272
|
// Check if file is untracked
|
|
1233
1273
|
if (statusOutput.startsWith('??')) {
|
|
@@ -1398,23 +1438,23 @@ class DashboardServer extends EventEmitter {
|
|
|
1398
1438
|
// Get branch and sync status via git
|
|
1399
1439
|
try {
|
|
1400
1440
|
const cwd = s.metadata.worktreePath || this.projectRoot;
|
|
1401
|
-
entry.branch = getChildProcess()
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1441
|
+
entry.branch = getChildProcess()
|
|
1442
|
+
.execFileSync('git', ['branch', '--show-current'], {
|
|
1443
|
+
cwd,
|
|
1444
|
+
encoding: 'utf8',
|
|
1445
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
1446
|
+
})
|
|
1447
|
+
.trim();
|
|
1406
1448
|
|
|
1407
1449
|
// Get ahead/behind counts relative to upstream
|
|
1408
1450
|
try {
|
|
1409
|
-
const counts = getChildProcess()
|
|
1410
|
-
'git',
|
|
1411
|
-
['rev-list', '--left-right', '--count', 'HEAD...@{u}'],
|
|
1412
|
-
{
|
|
1451
|
+
const counts = getChildProcess()
|
|
1452
|
+
.execFileSync('git', ['rev-list', '--left-right', '--count', 'HEAD...@{u}'], {
|
|
1413
1453
|
cwd,
|
|
1414
1454
|
encoding: 'utf8',
|
|
1415
1455
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
1416
|
-
}
|
|
1417
|
-
|
|
1456
|
+
})
|
|
1457
|
+
.trim();
|
|
1418
1458
|
const [ahead, behind] = counts.split(/\s+/).map(Number);
|
|
1419
1459
|
entry.ahead = ahead || 0;
|
|
1420
1460
|
entry.behind = behind || 0;
|
|
@@ -1454,7 +1494,9 @@ class DashboardServer extends EventEmitter {
|
|
|
1454
1494
|
}
|
|
1455
1495
|
|
|
1456
1496
|
// Validate the path stays within project root
|
|
1457
|
-
const pathResult = getValidatePaths().validatePath(filePath, this.projectRoot, {
|
|
1497
|
+
const pathResult = getValidatePaths().validatePath(filePath, this.projectRoot, {
|
|
1498
|
+
allowSymlinks: true,
|
|
1499
|
+
});
|
|
1458
1500
|
if (!pathResult.ok) {
|
|
1459
1501
|
session.send(getProtocol().createError('OPEN_FILE_ERROR', 'File path outside project'));
|
|
1460
1502
|
return;
|
|
@@ -1474,7 +1516,9 @@ class DashboardServer extends EventEmitter {
|
|
|
1474
1516
|
case 'cursor':
|
|
1475
1517
|
case 'windsurf': {
|
|
1476
1518
|
const gotoArg = lineNum ? `${fullPath}:${lineNum}` : fullPath;
|
|
1477
|
-
getChildProcess()
|
|
1519
|
+
getChildProcess()
|
|
1520
|
+
.spawn(editor, ['--goto', gotoArg], { detached: true, stdio: 'ignore' })
|
|
1521
|
+
.unref();
|
|
1478
1522
|
break;
|
|
1479
1523
|
}
|
|
1480
1524
|
case 'subl':
|
|
@@ -1491,11 +1535,17 @@ class DashboardServer extends EventEmitter {
|
|
|
1491
1535
|
}
|
|
1492
1536
|
|
|
1493
1537
|
session.send(
|
|
1494
|
-
getProtocol().createNotification(
|
|
1538
|
+
getProtocol().createNotification(
|
|
1539
|
+
'info',
|
|
1540
|
+
'Editor',
|
|
1541
|
+
`Opened ${require('path').basename(fullPath)}`
|
|
1542
|
+
)
|
|
1495
1543
|
);
|
|
1496
1544
|
} catch (error) {
|
|
1497
1545
|
console.error('[Open File Error]', error.message);
|
|
1498
|
-
session.send(
|
|
1546
|
+
session.send(
|
|
1547
|
+
getProtocol().createError('OPEN_FILE_ERROR', `Failed to open file: ${error.message}`)
|
|
1548
|
+
);
|
|
1499
1549
|
}
|
|
1500
1550
|
}
|
|
1501
1551
|
|
|
@@ -1508,10 +1558,15 @@ class DashboardServer extends EventEmitter {
|
|
|
1508
1558
|
// Validate cwd stays within project root
|
|
1509
1559
|
let safeCwd = this.projectRoot;
|
|
1510
1560
|
if (cwd) {
|
|
1511
|
-
const cwdResult = getValidatePaths().validatePath(cwd, this.projectRoot, {
|
|
1561
|
+
const cwdResult = getValidatePaths().validatePath(cwd, this.projectRoot, {
|
|
1562
|
+
allowSymlinks: true,
|
|
1563
|
+
});
|
|
1512
1564
|
if (!cwdResult.ok) {
|
|
1513
1565
|
session.send(
|
|
1514
|
-
getProtocol().createError(
|
|
1566
|
+
getProtocol().createError(
|
|
1567
|
+
'TERMINAL_ERROR',
|
|
1568
|
+
'Working directory must be within project root'
|
|
1569
|
+
)
|
|
1515
1570
|
);
|
|
1516
1571
|
return;
|
|
1517
1572
|
}
|
|
@@ -1684,7 +1739,9 @@ class DashboardServer extends EventEmitter {
|
|
|
1684
1739
|
}
|
|
1685
1740
|
|
|
1686
1741
|
if (!this._automationRunner) {
|
|
1687
|
-
session.send(
|
|
1742
|
+
session.send(
|
|
1743
|
+
getProtocol().createError('AUTOMATION_ERROR', 'Automation runner not initialized')
|
|
1744
|
+
);
|
|
1688
1745
|
return;
|
|
1689
1746
|
}
|
|
1690
1747
|
|
|
@@ -1692,12 +1749,18 @@ class DashboardServer extends EventEmitter {
|
|
|
1692
1749
|
// Check if already running
|
|
1693
1750
|
if (this._runningAutomations.has(automationId)) {
|
|
1694
1751
|
session.send(
|
|
1695
|
-
getProtocol().createNotification(
|
|
1752
|
+
getProtocol().createNotification(
|
|
1753
|
+
'warning',
|
|
1754
|
+
'Automation',
|
|
1755
|
+
`${automationId} is already running`
|
|
1756
|
+
)
|
|
1696
1757
|
);
|
|
1697
1758
|
return;
|
|
1698
1759
|
}
|
|
1699
1760
|
|
|
1700
|
-
session.send(
|
|
1761
|
+
session.send(
|
|
1762
|
+
getProtocol().createNotification('info', 'Automation', `Starting ${automationId}...`)
|
|
1763
|
+
);
|
|
1701
1764
|
|
|
1702
1765
|
// Run the automation (async)
|
|
1703
1766
|
const result = await this._automationRunner.run(automationId);
|
|
@@ -1705,23 +1768,39 @@ class DashboardServer extends EventEmitter {
|
|
|
1705
1768
|
// Send result notification
|
|
1706
1769
|
if (result.success) {
|
|
1707
1770
|
session.send(
|
|
1708
|
-
getProtocol().createNotification(
|
|
1771
|
+
getProtocol().createNotification(
|
|
1772
|
+
'success',
|
|
1773
|
+
'Automation',
|
|
1774
|
+
`${automationId} completed successfully`
|
|
1775
|
+
)
|
|
1709
1776
|
);
|
|
1710
1777
|
} else {
|
|
1711
1778
|
session.send(
|
|
1712
|
-
getProtocol().createNotification(
|
|
1779
|
+
getProtocol().createNotification(
|
|
1780
|
+
'error',
|
|
1781
|
+
'Automation',
|
|
1782
|
+
`${automationId} failed: ${result.error}`
|
|
1783
|
+
)
|
|
1713
1784
|
);
|
|
1714
1785
|
}
|
|
1715
1786
|
|
|
1716
1787
|
// Send final status
|
|
1717
|
-
session.send(
|
|
1788
|
+
session.send(
|
|
1789
|
+
getProtocol().createAutomationStatus(
|
|
1790
|
+
automationId,
|
|
1791
|
+
result.success ? 'idle' : 'error',
|
|
1792
|
+
result
|
|
1793
|
+
)
|
|
1794
|
+
);
|
|
1718
1795
|
|
|
1719
1796
|
// Refresh the list
|
|
1720
1797
|
this.sendAutomationList(session);
|
|
1721
1798
|
} catch (error) {
|
|
1722
1799
|
console.error('[Automation Error]', error.message);
|
|
1723
1800
|
session.send(getProtocol().createError('AUTOMATION_ERROR', 'Automation execution failed'));
|
|
1724
|
-
session.send(
|
|
1801
|
+
session.send(
|
|
1802
|
+
getProtocol().createAutomationStatus(automationId, 'error', { error: 'Execution failed' })
|
|
1803
|
+
);
|
|
1725
1804
|
}
|
|
1726
1805
|
}
|
|
1727
1806
|
|
|
@@ -1782,7 +1861,9 @@ class DashboardServer extends EventEmitter {
|
|
|
1782
1861
|
case 'accept':
|
|
1783
1862
|
// Mark as accepted and remove
|
|
1784
1863
|
item.status = 'accepted';
|
|
1785
|
-
session.send(
|
|
1864
|
+
session.send(
|
|
1865
|
+
getProtocol().createNotification('success', 'Inbox', `Accepted: ${item.title}`)
|
|
1866
|
+
);
|
|
1786
1867
|
this._inbox.delete(itemId);
|
|
1787
1868
|
break;
|
|
1788
1869
|
|
package/lib/flag-detection.js
CHANGED
|
@@ -219,7 +219,8 @@ function detectFromPs() {
|
|
|
219
219
|
// Get command line for this PID
|
|
220
220
|
let cmdline;
|
|
221
221
|
const cmdResult = executeCommandSync('ps', ['-p', String(pid), '-o', 'args='], {
|
|
222
|
-
timeout: 1000,
|
|
222
|
+
timeout: 1000,
|
|
223
|
+
fallback: null,
|
|
223
224
|
});
|
|
224
225
|
if (!cmdResult.ok || cmdResult.data === null) break;
|
|
225
226
|
cmdline = cmdResult.data;
|
|
@@ -239,7 +240,8 @@ function detectFromPs() {
|
|
|
239
240
|
|
|
240
241
|
// Get parent PID
|
|
241
242
|
const ppidResult = executeCommandSync('ps', ['-p', String(pid), '-o', 'ppid='], {
|
|
242
|
-
timeout: 1000,
|
|
243
|
+
timeout: 1000,
|
|
244
|
+
fallback: null,
|
|
243
245
|
});
|
|
244
246
|
if (!ppidResult.ok || ppidResult.data === null) break;
|
|
245
247
|
pid = parseInt(ppidResult.data, 10);
|
package/lib/git-operations.js
CHANGED
|
@@ -176,12 +176,14 @@ function getSessionPhase(session) {
|
|
|
176
176
|
try {
|
|
177
177
|
const mainBranch = getMainBranch(sessionPath);
|
|
178
178
|
const commitResult = git(['rev-list', '--count', `${mainBranch}..HEAD`], {
|
|
179
|
-
cwd: sessionPath,
|
|
179
|
+
cwd: sessionPath,
|
|
180
|
+
fallback: '0',
|
|
180
181
|
});
|
|
181
182
|
const commits = parseInt(commitResult.data, 10);
|
|
182
183
|
|
|
183
184
|
const statusResult = git(['status', '--porcelain'], {
|
|
184
|
-
cwd: sessionPath,
|
|
185
|
+
cwd: sessionPath,
|
|
186
|
+
fallback: '',
|
|
185
187
|
});
|
|
186
188
|
const status = statusResult.data;
|
|
187
189
|
|
package/lib/process-executor.js
CHANGED
|
@@ -91,13 +91,20 @@ function executeCommand(cmd, args = [], opts = {}) {
|
|
|
91
91
|
let stderr = '';
|
|
92
92
|
let timedOut = false;
|
|
93
93
|
|
|
94
|
-
const timer =
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
94
|
+
const timer =
|
|
95
|
+
timeout > 0
|
|
96
|
+
? setTimeout(() => {
|
|
97
|
+
timedOut = true;
|
|
98
|
+
proc.kill('SIGTERM');
|
|
99
|
+
}, timeout)
|
|
100
|
+
: null;
|
|
101
|
+
|
|
102
|
+
proc.stdout.on('data', chunk => {
|
|
103
|
+
stdout += chunk;
|
|
104
|
+
});
|
|
105
|
+
proc.stderr.on('data', chunk => {
|
|
106
|
+
stderr += chunk;
|
|
107
|
+
});
|
|
101
108
|
|
|
102
109
|
proc.on('error', err => {
|
|
103
110
|
if (timer) clearTimeout(timer);
|
|
@@ -115,7 +122,11 @@ function executeCommand(cmd, args = [], opts = {}) {
|
|
|
115
122
|
if (fallback !== undefined) {
|
|
116
123
|
resolve({ ok: true, data: fallback });
|
|
117
124
|
} else {
|
|
118
|
-
resolve({
|
|
125
|
+
resolve({
|
|
126
|
+
ok: false,
|
|
127
|
+
error: `Command timed out after ${timeout}ms: ${cmd}`,
|
|
128
|
+
exitCode: null,
|
|
129
|
+
});
|
|
119
130
|
}
|
|
120
131
|
return;
|
|
121
132
|
}
|
|
@@ -124,7 +135,11 @@ function executeCommand(cmd, args = [], opts = {}) {
|
|
|
124
135
|
if (fallback !== undefined) {
|
|
125
136
|
resolve({ ok: true, data: fallback });
|
|
126
137
|
} else {
|
|
127
|
-
const result = {
|
|
138
|
+
const result = {
|
|
139
|
+
ok: false,
|
|
140
|
+
error: `Command failed: ${cmd} ${args.join(' ')} (exit ${code})`,
|
|
141
|
+
exitCode: code,
|
|
142
|
+
};
|
|
128
143
|
if (captureStderr) {
|
|
129
144
|
result.stderr = trim ? stderr.trim() : stderr;
|
|
130
145
|
}
|
package/lib/skill-loader.js
CHANGED
|
@@ -57,13 +57,21 @@ function parseSkillFrontmatter(content) {
|
|
|
57
57
|
let value = trimmed.substring(colonIndex + 1).trim();
|
|
58
58
|
|
|
59
59
|
// Remove surrounding quotes
|
|
60
|
-
if (
|
|
61
|
-
|
|
60
|
+
if (
|
|
61
|
+
(value.startsWith('"') && value.endsWith('"')) ||
|
|
62
|
+
(value.startsWith("'") && value.endsWith("'"))
|
|
63
|
+
) {
|
|
62
64
|
value = value.slice(1, -1);
|
|
63
65
|
}
|
|
64
66
|
|
|
65
67
|
// Type coercion for known fields
|
|
66
|
-
if (
|
|
68
|
+
if (
|
|
69
|
+
key === 'version' ||
|
|
70
|
+
key === 'model' ||
|
|
71
|
+
key === 'category' ||
|
|
72
|
+
key === 'name' ||
|
|
73
|
+
key === 'type'
|
|
74
|
+
) {
|
|
67
75
|
result.metadata[key] = value;
|
|
68
76
|
} else if (value === 'true') {
|
|
69
77
|
result.metadata[key] = true;
|