agileflow 3.0.2 → 3.2.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/README.md +58 -86
- package/lib/dashboard-automations.js +130 -0
- package/lib/dashboard-git.js +254 -0
- package/lib/dashboard-inbox.js +64 -0
- package/lib/dashboard-protocol.js +1 -0
- package/lib/dashboard-server.js +114 -924
- package/lib/dashboard-session.js +136 -0
- package/lib/dashboard-status.js +72 -0
- package/lib/dashboard-terminal.js +354 -0
- package/lib/dashboard-websocket.js +88 -0
- package/lib/drivers/codex-driver.ts +4 -4
- package/lib/feedback.js +9 -2
- package/lib/lazy-require.js +59 -0
- package/lib/logger.js +106 -0
- package/package.json +4 -2
- package/scripts/agileflow-configure.js +14 -2
- package/scripts/agileflow-welcome.js +450 -459
- package/scripts/claude-tmux.sh +113 -5
- package/scripts/context-loader.js +4 -9
- package/scripts/lib/command-prereqs.js +280 -0
- package/scripts/lib/configure-detect.js +92 -2
- package/scripts/lib/configure-features.js +411 -1
- package/scripts/lib/context-formatter.js +468 -233
- package/scripts/lib/context-loader.js +27 -15
- package/scripts/lib/damage-control-utils.js +8 -1
- package/scripts/lib/feature-catalog.js +321 -0
- package/scripts/lib/portable-tasks-cli.js +274 -0
- package/scripts/lib/portable-tasks.js +479 -0
- package/scripts/lib/signal-detectors.js +1 -1
- package/scripts/lib/team-events.js +86 -1
- package/scripts/obtain-context.js +28 -4
- package/scripts/smart-detect.js +17 -0
- package/scripts/strip-ai-attribution.js +63 -0
- package/scripts/team-manager.js +90 -0
- package/scripts/welcome-deferred.js +437 -0
- package/src/core/agents/legal-analyzer-a11y.md +110 -0
- package/src/core/agents/legal-analyzer-ai.md +117 -0
- package/src/core/agents/legal-analyzer-consumer.md +108 -0
- package/src/core/agents/legal-analyzer-content.md +113 -0
- package/src/core/agents/legal-analyzer-international.md +115 -0
- package/src/core/agents/legal-analyzer-licensing.md +115 -0
- package/src/core/agents/legal-analyzer-privacy.md +108 -0
- package/src/core/agents/legal-analyzer-security.md +112 -0
- package/src/core/agents/legal-analyzer-terms.md +111 -0
- package/src/core/agents/legal-consensus.md +242 -0
- package/src/core/agents/perf-analyzer-assets.md +174 -0
- package/src/core/agents/perf-analyzer-bundle.md +165 -0
- package/src/core/agents/perf-analyzer-caching.md +160 -0
- package/src/core/agents/perf-analyzer-compute.md +165 -0
- package/src/core/agents/perf-analyzer-memory.md +182 -0
- package/src/core/agents/perf-analyzer-network.md +157 -0
- package/src/core/agents/perf-analyzer-queries.md +155 -0
- package/src/core/agents/perf-analyzer-rendering.md +156 -0
- package/src/core/agents/perf-consensus.md +280 -0
- package/src/core/agents/security-analyzer-api.md +199 -0
- package/src/core/agents/security-analyzer-auth.md +160 -0
- package/src/core/agents/security-analyzer-authz.md +168 -0
- package/src/core/agents/security-analyzer-deps.md +147 -0
- package/src/core/agents/security-analyzer-infra.md +176 -0
- package/src/core/agents/security-analyzer-injection.md +148 -0
- package/src/core/agents/security-analyzer-input.md +191 -0
- package/src/core/agents/security-analyzer-secrets.md +175 -0
- package/src/core/agents/security-consensus.md +276 -0
- package/src/core/agents/team-lead.md +50 -13
- package/src/core/agents/test-analyzer-assertions.md +181 -0
- package/src/core/agents/test-analyzer-coverage.md +183 -0
- package/src/core/agents/test-analyzer-fragility.md +185 -0
- package/src/core/agents/test-analyzer-integration.md +155 -0
- package/src/core/agents/test-analyzer-maintenance.md +173 -0
- package/src/core/agents/test-analyzer-mocking.md +178 -0
- package/src/core/agents/test-analyzer-patterns.md +189 -0
- package/src/core/agents/test-analyzer-structure.md +177 -0
- package/src/core/agents/test-consensus.md +294 -0
- package/src/core/commands/audit/legal.md +446 -0
- package/src/core/commands/{logic/audit.md → audit/logic.md} +12 -12
- package/src/core/commands/audit/performance.md +443 -0
- package/src/core/commands/audit/security.md +443 -0
- package/src/core/commands/audit/test.md +442 -0
- package/src/core/commands/babysit.md +505 -463
- package/src/core/commands/configure.md +18 -33
- package/src/core/commands/research/ask.md +42 -9
- package/src/core/commands/research/import.md +14 -8
- package/src/core/commands/research/list.md +17 -16
- package/src/core/commands/research/synthesize.md +8 -8
- package/src/core/commands/research/view.md +28 -4
- package/src/core/commands/team/start.md +36 -7
- package/src/core/commands/team/stop.md +5 -2
- package/src/core/commands/whats-new.md +2 -2
- package/src/core/experts/devops/expertise.yaml +13 -2
- package/src/core/experts/documentation/expertise.yaml +26 -4
- package/src/core/profiles/COMPARISON.md +170 -0
- package/src/core/profiles/README.md +178 -0
- package/src/core/profiles/claude-code.yaml +111 -0
- package/src/core/profiles/codex.yaml +103 -0
- package/src/core/profiles/cursor.yaml +134 -0
- package/src/core/profiles/examples.js +250 -0
- package/src/core/profiles/loader.js +235 -0
- package/src/core/profiles/windsurf.yaml +159 -0
- package/src/core/teams/logic-audit.json +6 -0
- package/src/core/teams/perf-audit.json +71 -0
- package/src/core/teams/security-audit.json +71 -0
- package/src/core/teams/test-audit.json +71 -0
- package/src/core/templates/command-prerequisites.yaml +169 -0
- package/src/core/templates/damage-control-patterns.yaml +9 -0
- package/tools/cli/installers/ide/_base-ide.js +33 -3
- package/tools/cli/installers/ide/claude-code.js +2 -67
- package/tools/cli/installers/ide/codex.js +9 -9
- package/tools/cli/installers/ide/cursor.js +165 -4
- package/tools/cli/installers/ide/windsurf.js +237 -6
- package/tools/cli/lib/content-transformer.js +234 -9
- package/tools/cli/lib/docs-setup.js +1 -1
- package/tools/cli/lib/ide-generator.js +357 -0
- package/tools/cli/lib/ide-registry.js +2 -2
- package/scripts/tmux-task-name.sh +0 -75
- package/scripts/tmux-task-watcher.sh +0 -177
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.2.0] - 2026-02-21
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- Cross-IDE capability profiles, multi-agent audit systems, and AI attribution blocking
|
|
14
|
+
|
|
15
|
+
## [3.1.0] - 2026-02-14
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
- Legal audit system, native Agent Teams integration, and startup performance improvements
|
|
19
|
+
|
|
10
20
|
## [3.0.2] - 2026-02-14
|
|
11
21
|
|
|
12
22
|
### Added
|
package/README.md
CHANGED
|
@@ -3,30 +3,23 @@
|
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/agileflow)
|
|
6
|
-
[](docs/
|
|
6
|
+
[](https://docs.agileflow.projectquestorg.com/docs/commands)
|
|
7
|
+
[](https://docs.agileflow.projectquestorg.com/docs/agents)
|
|
8
|
+
[](https://docs.agileflow.projectquestorg.com/docs/features/skills)
|
|
9
9
|
|
|
10
|
-
**AI-driven agile development for Claude Code, Cursor, Windsurf, OpenAI Codex
|
|
10
|
+
**AI-driven agile development for Claude Code, Cursor, Windsurf, OpenAI Codex, and more.** Combining Scrum, Kanban, ADRs, and docs-as-code principles into one framework-agnostic system.
|
|
11
11
|
|
|
12
12
|
---
|
|
13
13
|
|
|
14
14
|
## Quick Start
|
|
15
15
|
|
|
16
|
-
### Installation
|
|
17
|
-
|
|
18
16
|
```bash
|
|
19
17
|
npx agileflow@latest setup
|
|
20
18
|
```
|
|
21
19
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
**Updates:**
|
|
25
|
-
```bash
|
|
26
|
-
npx agileflow@latest update
|
|
27
|
-
```
|
|
20
|
+
The `npx` command always fetches the latest version.
|
|
28
21
|
|
|
29
|
-
|
|
22
|
+
**Updates:** `npx agileflow@latest update`
|
|
30
23
|
|
|
31
24
|
```bash
|
|
32
25
|
/agileflow:help # View all commands
|
|
@@ -39,25 +32,21 @@ npx agileflow@latest update
|
|
|
39
32
|
| IDE | Status | Config Location |
|
|
40
33
|
|-----|--------|-----------------|
|
|
41
34
|
| Claude Code | Supported | `.claude/commands/agileflow/` |
|
|
42
|
-
| Cursor | Supported | `.cursor/
|
|
35
|
+
| Cursor | Supported | `.cursor/commands/agileflow/` |
|
|
43
36
|
| Windsurf | Supported | `.windsurf/workflows/agileflow/` |
|
|
44
|
-
| OpenAI Codex
|
|
37
|
+
| OpenAI Codex | Supported | `.codex/skills/` and `~/.codex/prompts/` |
|
|
45
38
|
|
|
46
39
|
---
|
|
47
40
|
|
|
48
41
|
## Why AgileFlow?
|
|
49
42
|
|
|
50
|
-
AgileFlow
|
|
51
|
-
|
|
52
|
-
- **Agile (Scrum/Kanban)** - Break work into Epics → Stories → Acceptance Criteria
|
|
53
|
-
- **ADRs** - Record architectural decisions so future teams don't re-debate
|
|
54
|
-
- **Docs-as-Code** - Humans and AI agents coordinate via versioned files
|
|
43
|
+
Traditional project management tools create friction between planning and execution. AgileFlow eliminates this gap by embedding project management directly into your AI-assisted coding workflow.
|
|
55
44
|
|
|
56
|
-
**
|
|
57
|
-
-
|
|
58
|
-
-
|
|
59
|
-
-
|
|
60
|
-
- Works with any tech stack
|
|
45
|
+
- **No context switching** - Manage epics, stories, and status without leaving your terminal
|
|
46
|
+
- **AI-native workflows** - Purpose-built for Claude Code's capabilities
|
|
47
|
+
- **Docs-as-code** - All project artifacts live in your repository as plain text
|
|
48
|
+
- **Intelligent agents** - 55 specialized AI agents for different domains
|
|
49
|
+
- **Framework-agnostic** - Works with any tech stack
|
|
61
50
|
|
|
62
51
|
---
|
|
63
52
|
|
|
@@ -65,86 +54,68 @@ AgileFlow combines three proven methodologies:
|
|
|
65
54
|
|
|
66
55
|
| Component | Count | Description |
|
|
67
56
|
|-----------|-------|-------------|
|
|
68
|
-
| [Commands](docs/
|
|
69
|
-
| [Agents/Experts](docs/
|
|
70
|
-
| [Skills](docs/
|
|
57
|
+
| [Commands](https://docs.agileflow.projectquestorg.com/docs/commands) | 98 | Slash commands for agile workflows |
|
|
58
|
+
| [Agents/Experts](https://docs.agileflow.projectquestorg.com/docs/agents) | 83 | Specialized agents with self-improving knowledge bases |
|
|
59
|
+
| [Skills](https://docs.agileflow.projectquestorg.com/docs/features/skills) | Dynamic | Generated on-demand with `/agileflow:skill:create` |
|
|
71
60
|
|
|
72
61
|
---
|
|
73
62
|
|
|
74
|
-
##
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
- [
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
- [
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
- [
|
|
92
|
-
|
|
93
|
-
|
|
63
|
+
## Features
|
|
64
|
+
|
|
65
|
+
| Feature | Description | Docs |
|
|
66
|
+
|---------|-------------|------|
|
|
67
|
+
| Agent Expertise | Self-improving agents that maintain domain knowledge | [Learn more](https://docs.agileflow.projectquestorg.com/docs/features/agent-expertise-system) |
|
|
68
|
+
| Agent Teams | Multi-domain expert coordination with quality gates | [Learn more](https://docs.agileflow.projectquestorg.com/docs/features/agent-teams) |
|
|
69
|
+
| Skills System | Custom AI prompts that learn from your feedback | [Learn more](https://docs.agileflow.projectquestorg.com/docs/features/skills) |
|
|
70
|
+
| Parallel Sessions | Isolated workspaces with boundary protection | [Learn more](https://docs.agileflow.projectquestorg.com/docs/features/parallel-sessions) |
|
|
71
|
+
| Loop Mode | Autonomous story execution until epic completion | [Learn more](https://docs.agileflow.projectquestorg.com/docs/features/loop-mode) |
|
|
72
|
+
| AI Council | Three-perspective strategic decision analysis | [Learn more](https://docs.agileflow.projectquestorg.com/docs/commands/council) |
|
|
73
|
+
| Logic Audit | Multi-agent logic bug detection with consensus voting | [Learn more](https://docs.agileflow.projectquestorg.com/docs/commands/logic-audit) |
|
|
74
|
+
| Damage Control | Block destructive commands with PreToolUse hooks | [Learn more](https://docs.agileflow.projectquestorg.com/docs/features/damage-control) |
|
|
75
|
+
| Smart Detection | Contextual feature recommendations with 42 detectors | [Learn more](https://docs.agileflow.projectquestorg.com/docs/features/smart-detect) |
|
|
76
|
+
| Visual Mode | Screenshot verification for UI development | [Learn more](https://docs.agileflow.projectquestorg.com/docs/features/visual-mode) |
|
|
77
|
+
| Context Preservation | Preserve state during automatic context compaction | [Learn more](https://docs.agileflow.projectquestorg.com/docs/features/compact-context) |
|
|
78
|
+
| Research Pipeline | Structured research workflow with synthesis | [Learn more](https://docs.agileflow.projectquestorg.com/docs/commands/research) |
|
|
79
|
+
| Automations | Scheduled recurring tasks without a daemon | [Learn more](https://docs.agileflow.projectquestorg.com/docs/commands/automate) |
|
|
80
|
+
| Tmux Integration | Multi-window terminal sessions with keybindings and status bar | [Learn more](https://docs.agileflow.projectquestorg.com/docs/features/tmux-keybindings) |
|
|
81
|
+
| IDE Integrations | Claude Code, Cursor, Windsurf, OpenAI Codex support | [Learn more](https://docs.agileflow.projectquestorg.com/docs/features/ide-integrations) |
|
|
82
|
+
|
|
83
|
+
See the [full features overview](https://docs.agileflow.projectquestorg.com/docs/features) for details.
|
|
94
84
|
|
|
95
85
|
---
|
|
96
86
|
|
|
97
87
|
## Examples
|
|
98
88
|
|
|
99
|
-
### Create an Epic with Stories
|
|
100
89
|
```bash
|
|
90
|
+
# Create an epic
|
|
101
91
|
/agileflow:epic EPIC=EP-0001 TITLE="User Authentication" OWNER=AG-API GOAL="Secure login"
|
|
102
|
-
```
|
|
103
92
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
/agileflow:assign STORY=US-0001 NEW_OWNER=AG-UI NEW_STATUS=in-progress
|
|
107
|
-
# ... implement ...
|
|
108
|
-
/agileflow:status STORY=US-0001 STATUS=in-review SUMMARY="Login form complete"
|
|
109
|
-
```
|
|
93
|
+
# Work on a story
|
|
94
|
+
/agileflow:babysit
|
|
110
95
|
|
|
111
|
-
|
|
112
|
-
```bash
|
|
96
|
+
# Multi-expert analysis
|
|
113
97
|
/agileflow:multi-expert Is this authentication implementation secure?
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
### Work in Parallel Sessions
|
|
117
|
-
```bash
|
|
118
|
-
/agileflow:session:new # Create isolated workspace
|
|
119
|
-
/agileflow:session:status # View all sessions
|
|
120
|
-
```
|
|
121
98
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
## Project Structure
|
|
125
|
-
|
|
126
|
-
After running `agileflow setup`:
|
|
99
|
+
# AI Council for strategic decisions
|
|
100
|
+
/agileflow:council Should we use microservices or a monolith?
|
|
127
101
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
00-meta/ # Templates, conventions
|
|
131
|
-
01-brainstorming/ # Ideas and sketches
|
|
132
|
-
02-practices/ # Testing, git, CI practices
|
|
133
|
-
03-decisions/ # ADRs
|
|
134
|
-
04-architecture/ # Architecture documentation
|
|
135
|
-
05-epics/ # Epic definitions
|
|
136
|
-
06-stories/ # User stories
|
|
137
|
-
07-testing/ # Test cases
|
|
138
|
-
08-project/ # Roadmap, backlog
|
|
139
|
-
09-agents/ # Agent status, message bus
|
|
140
|
-
10-research/ # Research notes
|
|
102
|
+
# Parallel sessions
|
|
103
|
+
/agileflow:session:new
|
|
141
104
|
```
|
|
142
105
|
|
|
143
106
|
---
|
|
144
107
|
|
|
145
|
-
##
|
|
108
|
+
## Documentation
|
|
109
|
+
|
|
110
|
+
Full documentation at **[docs.agileflow.projectquestorg.com](https://docs.agileflow.projectquestorg.com)**.
|
|
146
111
|
|
|
147
|
-
|
|
112
|
+
| Section | Link |
|
|
113
|
+
|---------|------|
|
|
114
|
+
| Getting Started | [docs.agileflow.projectquestorg.com/docs/getting-started](https://docs.agileflow.projectquestorg.com/docs/getting-started) |
|
|
115
|
+
| Installation | [docs.agileflow.projectquestorg.com/docs/installation](https://docs.agileflow.projectquestorg.com/docs/installation) |
|
|
116
|
+
| Commands | [docs.agileflow.projectquestorg.com/docs/commands](https://docs.agileflow.projectquestorg.com/docs/commands) |
|
|
117
|
+
| Agents | [docs.agileflow.projectquestorg.com/docs/agents](https://docs.agileflow.projectquestorg.com/docs/agents) |
|
|
118
|
+
| Features | [docs.agileflow.projectquestorg.com/docs/features](https://docs.agileflow.projectquestorg.com/docs/features) |
|
|
148
119
|
|
|
149
120
|
---
|
|
150
121
|
|
|
@@ -154,4 +125,5 @@ MIT
|
|
|
154
125
|
|
|
155
126
|
## Support
|
|
156
127
|
|
|
157
|
-
|
|
128
|
+
- [Documentation](https://docs.agileflow.projectquestorg.com) - Full docs site
|
|
129
|
+
- [GitHub Issues](https://github.com/projectquestorg/AgileFlow/issues) - Bug reports and feature requests
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* dashboard-automations.js - Automation Scheduling and Management
|
|
5
|
+
*
|
|
6
|
+
* Handles automation registry, scheduling (next run calculation),
|
|
7
|
+
* run/stop operations, and inbox integration.
|
|
8
|
+
* Extracted from dashboard-server.js for testability.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Calculate next run time for an automation
|
|
13
|
+
* @param {Object} automation - Automation config with enabled and schedule properties
|
|
14
|
+
* @returns {string|null} - ISO timestamp, human-readable string, or null
|
|
15
|
+
*/
|
|
16
|
+
function calculateNextRun(automation) {
|
|
17
|
+
if (!automation.enabled || !automation.schedule) return null;
|
|
18
|
+
|
|
19
|
+
const now = new Date();
|
|
20
|
+
const schedule = automation.schedule;
|
|
21
|
+
|
|
22
|
+
switch (schedule.type) {
|
|
23
|
+
case 'on_session':
|
|
24
|
+
return 'Every session';
|
|
25
|
+
case 'daily': {
|
|
26
|
+
// Next day at midnight (or specified hour)
|
|
27
|
+
const nextDaily = new Date(now);
|
|
28
|
+
nextDaily.setDate(nextDaily.getDate() + 1);
|
|
29
|
+
nextDaily.setHours(schedule.hour || 0, 0, 0, 0);
|
|
30
|
+
return nextDaily.toISOString();
|
|
31
|
+
}
|
|
32
|
+
case 'weekly': {
|
|
33
|
+
// Next occurrence of the specified day
|
|
34
|
+
let targetDay = 0;
|
|
35
|
+
if (typeof schedule.day === 'string') {
|
|
36
|
+
const idx = [
|
|
37
|
+
'sunday',
|
|
38
|
+
'monday',
|
|
39
|
+
'tuesday',
|
|
40
|
+
'wednesday',
|
|
41
|
+
'thursday',
|
|
42
|
+
'friday',
|
|
43
|
+
'saturday',
|
|
44
|
+
].indexOf(schedule.day.toLowerCase());
|
|
45
|
+
targetDay = idx >= 0 ? idx : 0; // Default to Sunday if invalid name
|
|
46
|
+
} else {
|
|
47
|
+
targetDay = schedule.day || 0;
|
|
48
|
+
}
|
|
49
|
+
const nextWeekly = new Date(now);
|
|
50
|
+
const daysUntil = (targetDay - now.getDay() + 7) % 7 || 7;
|
|
51
|
+
nextWeekly.setDate(nextWeekly.getDate() + daysUntil);
|
|
52
|
+
nextWeekly.setHours(schedule.hour || 0, 0, 0, 0);
|
|
53
|
+
return nextWeekly.toISOString();
|
|
54
|
+
}
|
|
55
|
+
case 'monthly': {
|
|
56
|
+
// Next occurrence of the specified date
|
|
57
|
+
const nextMonthly = new Date(now);
|
|
58
|
+
const targetDate = schedule.date || 1;
|
|
59
|
+
if (now.getDate() >= targetDate) {
|
|
60
|
+
nextMonthly.setMonth(nextMonthly.getMonth() + 1);
|
|
61
|
+
}
|
|
62
|
+
nextMonthly.setDate(targetDate);
|
|
63
|
+
nextMonthly.setHours(schedule.hour || 0, 0, 0, 0);
|
|
64
|
+
return nextMonthly.toISOString();
|
|
65
|
+
}
|
|
66
|
+
case 'interval': {
|
|
67
|
+
const hours = schedule.hours || 24;
|
|
68
|
+
return `Every ${hours} hour${hours > 1 ? 's' : ''}`;
|
|
69
|
+
}
|
|
70
|
+
default:
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Create an inbox item from an automation result
|
|
77
|
+
* @param {string} automationId - Automation ID
|
|
78
|
+
* @param {Object} result - Run result
|
|
79
|
+
* @param {string|undefined} automationName - Display name of the automation
|
|
80
|
+
* @returns {Object} - Inbox item
|
|
81
|
+
*/
|
|
82
|
+
function createInboxItem(automationId, result, automationName) {
|
|
83
|
+
const itemId = `inbox_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
id: itemId,
|
|
87
|
+
automationId,
|
|
88
|
+
title: automationName || automationId,
|
|
89
|
+
summary: result.success
|
|
90
|
+
? result.output?.slice(0, 200) || 'Completed successfully'
|
|
91
|
+
: result.error?.slice(0, 200) || 'Failed',
|
|
92
|
+
timestamp: new Date().toISOString(),
|
|
93
|
+
status: 'unread',
|
|
94
|
+
result: {
|
|
95
|
+
success: result.success,
|
|
96
|
+
output: result.output,
|
|
97
|
+
error: result.error,
|
|
98
|
+
duration_ms: result.duration_ms,
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Enrich automation list with running status and next run time
|
|
105
|
+
* @param {Array} automations - Raw automation list
|
|
106
|
+
* @param {Map} runningAutomations - Map of currently running automation IDs
|
|
107
|
+
* @param {Object} registry - Automation registry (for getRunHistory)
|
|
108
|
+
* @returns {Array} - Enriched automation list
|
|
109
|
+
*/
|
|
110
|
+
function enrichAutomationList(automations, runningAutomations, registry) {
|
|
111
|
+
return automations.map(automation => {
|
|
112
|
+
const isRunning = runningAutomations.has(automation.id);
|
|
113
|
+
const lastRun = registry.getRunHistory(automation.id, 1)[0];
|
|
114
|
+
const nextRun = calculateNextRun(automation);
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
...automation,
|
|
118
|
+
status: isRunning ? 'running' : automation.enabled ? 'idle' : 'disabled',
|
|
119
|
+
lastRun: lastRun?.at,
|
|
120
|
+
lastRunSuccess: lastRun?.success,
|
|
121
|
+
nextRun,
|
|
122
|
+
};
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
module.exports = {
|
|
127
|
+
calculateNextRun,
|
|
128
|
+
createInboxItem,
|
|
129
|
+
enrichAutomationList,
|
|
130
|
+
};
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* dashboard-git.js - Git Operations for Dashboard
|
|
5
|
+
*
|
|
6
|
+
* Git status, diff, and action handlers used by the dashboard server.
|
|
7
|
+
* Extracted from dashboard-server.js for testability.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// Lazy-loaded dependencies
|
|
11
|
+
let _childProcess, _validatePaths;
|
|
12
|
+
|
|
13
|
+
function getChildProcess() {
|
|
14
|
+
if (!_childProcess) _childProcess = require('child_process');
|
|
15
|
+
return _childProcess;
|
|
16
|
+
}
|
|
17
|
+
function getValidatePaths() {
|
|
18
|
+
if (!_validatePaths) _validatePaths = require('./validate-paths');
|
|
19
|
+
return _validatePaths;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Get current git status
|
|
24
|
+
* @param {string} projectRoot - Project root directory
|
|
25
|
+
* @returns {{ branch: string, staged: Array, unstaged: Array }}
|
|
26
|
+
*/
|
|
27
|
+
function getGitStatus(projectRoot) {
|
|
28
|
+
try {
|
|
29
|
+
const branch = getChildProcess()
|
|
30
|
+
.execFileSync('git', ['branch', '--show-current'], {
|
|
31
|
+
cwd: projectRoot,
|
|
32
|
+
encoding: 'utf8',
|
|
33
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
34
|
+
})
|
|
35
|
+
.trim();
|
|
36
|
+
|
|
37
|
+
const statusOutput = getChildProcess().execFileSync('git', ['status', '--porcelain'], {
|
|
38
|
+
cwd: projectRoot,
|
|
39
|
+
encoding: 'utf8',
|
|
40
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const staged = [];
|
|
44
|
+
const unstaged = [];
|
|
45
|
+
|
|
46
|
+
for (const line of statusOutput.split('\n').filter(Boolean)) {
|
|
47
|
+
if (line.length < 4) continue; // Skip malformed lines (need XY + space + filename)
|
|
48
|
+
const indexStatus = line[0];
|
|
49
|
+
const workTreeStatus = line[1];
|
|
50
|
+
const file = line.slice(3);
|
|
51
|
+
|
|
52
|
+
// Parse the status character to a descriptive status
|
|
53
|
+
const parseStatus = char => {
|
|
54
|
+
switch (char) {
|
|
55
|
+
case 'A':
|
|
56
|
+
return 'added';
|
|
57
|
+
case 'M':
|
|
58
|
+
return 'modified';
|
|
59
|
+
case 'D':
|
|
60
|
+
return 'deleted';
|
|
61
|
+
case 'R':
|
|
62
|
+
return 'renamed';
|
|
63
|
+
case 'C':
|
|
64
|
+
return 'copied';
|
|
65
|
+
case '?':
|
|
66
|
+
return 'untracked';
|
|
67
|
+
default:
|
|
68
|
+
return 'modified';
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
if (indexStatus !== ' ' && indexStatus !== '?') {
|
|
73
|
+
staged.push({ path: file, file, status: parseStatus(indexStatus) });
|
|
74
|
+
}
|
|
75
|
+
if (workTreeStatus !== ' ') {
|
|
76
|
+
unstaged.push({
|
|
77
|
+
path: file,
|
|
78
|
+
file,
|
|
79
|
+
status: workTreeStatus === '?' ? 'untracked' : parseStatus(workTreeStatus),
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return { branch, staged, unstaged };
|
|
85
|
+
} catch {
|
|
86
|
+
return { branch: 'unknown', staged: [], unstaged: [] };
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Get diff for a specific file
|
|
92
|
+
* @param {string} filePath - Path to the file
|
|
93
|
+
* @param {string} projectRoot - Project root directory
|
|
94
|
+
* @param {boolean} staged - Whether to get staged diff
|
|
95
|
+
* @returns {string} - The diff content
|
|
96
|
+
*/
|
|
97
|
+
function getFileDiff(filePath, projectRoot, staged = false) {
|
|
98
|
+
// Validate filePath stays within project root
|
|
99
|
+
const pathResult = getValidatePaths().validatePath(filePath, projectRoot, {
|
|
100
|
+
allowSymlinks: true,
|
|
101
|
+
});
|
|
102
|
+
if (!pathResult.ok) {
|
|
103
|
+
return '';
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
const diffArgs = staged ? ['diff', '--cached', '--', filePath] : ['diff', '--', filePath];
|
|
108
|
+
|
|
109
|
+
const diff = getChildProcess().execFileSync('git', diffArgs, {
|
|
110
|
+
cwd: projectRoot,
|
|
111
|
+
encoding: 'utf8',
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// If no diff, file might be untracked - show entire file content as addition
|
|
115
|
+
if (!diff && !staged) {
|
|
116
|
+
const statusOutput = getChildProcess()
|
|
117
|
+
.execFileSync('git', ['status', '--porcelain', '--', filePath], {
|
|
118
|
+
cwd: projectRoot,
|
|
119
|
+
encoding: 'utf8',
|
|
120
|
+
})
|
|
121
|
+
.trim();
|
|
122
|
+
|
|
123
|
+
// Check if file is untracked
|
|
124
|
+
if (statusOutput.startsWith('??')) {
|
|
125
|
+
try {
|
|
126
|
+
const content = require('fs').readFileSync(
|
|
127
|
+
require('path').join(projectRoot, filePath),
|
|
128
|
+
'utf8'
|
|
129
|
+
);
|
|
130
|
+
// Format as a new file diff
|
|
131
|
+
const lines = content.split('\n');
|
|
132
|
+
return [
|
|
133
|
+
`diff --git a/${filePath} b/${filePath}`,
|
|
134
|
+
`new file mode 100644`,
|
|
135
|
+
`--- /dev/null`,
|
|
136
|
+
`+++ b/${filePath}`,
|
|
137
|
+
`@@ -0,0 +1,${lines.length} @@`,
|
|
138
|
+
...lines.map(line => `+${line}`),
|
|
139
|
+
].join('\n');
|
|
140
|
+
} catch {
|
|
141
|
+
return '';
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return diff;
|
|
147
|
+
} catch (error) {
|
|
148
|
+
console.error('[Diff Error]', error.message);
|
|
149
|
+
return '';
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Parse diff statistics from diff content
|
|
155
|
+
* @param {string} diff - The diff content
|
|
156
|
+
* @returns {{ additions: number, deletions: number }}
|
|
157
|
+
*/
|
|
158
|
+
function parseDiffStats(diff) {
|
|
159
|
+
let additions = 0;
|
|
160
|
+
let deletions = 0;
|
|
161
|
+
|
|
162
|
+
for (const line of diff.split('\n')) {
|
|
163
|
+
if (line.startsWith('+') && !line.startsWith('+++')) {
|
|
164
|
+
additions++;
|
|
165
|
+
} else if (line.startsWith('-') && !line.startsWith('---')) {
|
|
166
|
+
deletions++;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return { additions, deletions };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Execute a git action (stage, unstage, revert, commit)
|
|
175
|
+
* @param {string} type - Action type (git_stage, git_unstage, git_revert, git_commit)
|
|
176
|
+
* @param {string} projectRoot - Project root directory
|
|
177
|
+
* @param {Object} options - Action options
|
|
178
|
+
* @param {string[]} [options.files] - Files to operate on
|
|
179
|
+
* @param {string} [options.commitMessage] - Commit message (for git_commit)
|
|
180
|
+
* @param {Object} protocol - Protocol module reference
|
|
181
|
+
*/
|
|
182
|
+
function handleGitAction(type, projectRoot, options, protocol) {
|
|
183
|
+
const { files, commitMessage } = options;
|
|
184
|
+
|
|
185
|
+
// Validate file paths - reject path traversal attempts
|
|
186
|
+
if (files && files.length > 0) {
|
|
187
|
+
for (const f of files) {
|
|
188
|
+
if (typeof f !== 'string' || f.includes('\0')) {
|
|
189
|
+
throw new Error('Invalid file path');
|
|
190
|
+
}
|
|
191
|
+
const resolved = require('path').resolve(projectRoot, f);
|
|
192
|
+
if (!resolved.startsWith(projectRoot)) {
|
|
193
|
+
throw new Error('File path outside project');
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Validate commit message
|
|
199
|
+
if (commitMessage !== undefined && commitMessage !== null) {
|
|
200
|
+
if (
|
|
201
|
+
typeof commitMessage !== 'string' ||
|
|
202
|
+
commitMessage.length > 10000 ||
|
|
203
|
+
commitMessage.includes('\0')
|
|
204
|
+
) {
|
|
205
|
+
throw new Error('Invalid commit message');
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const fileArgs = files && files.length > 0 ? files : null;
|
|
210
|
+
|
|
211
|
+
switch (type) {
|
|
212
|
+
case protocol.InboundMessageType.GIT_STAGE:
|
|
213
|
+
if (fileArgs) {
|
|
214
|
+
getChildProcess().execFileSync('git', ['add', '--', ...fileArgs], {
|
|
215
|
+
cwd: projectRoot,
|
|
216
|
+
});
|
|
217
|
+
} else {
|
|
218
|
+
getChildProcess().execFileSync('git', ['add', '-A'], { cwd: projectRoot });
|
|
219
|
+
}
|
|
220
|
+
break;
|
|
221
|
+
case protocol.InboundMessageType.GIT_UNSTAGE:
|
|
222
|
+
if (fileArgs) {
|
|
223
|
+
getChildProcess().execFileSync('git', ['restore', '--staged', '--', ...fileArgs], {
|
|
224
|
+
cwd: projectRoot,
|
|
225
|
+
});
|
|
226
|
+
} else {
|
|
227
|
+
getChildProcess().execFileSync('git', ['restore', '--staged', '.'], {
|
|
228
|
+
cwd: projectRoot,
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
break;
|
|
232
|
+
case protocol.InboundMessageType.GIT_REVERT:
|
|
233
|
+
if (fileArgs) {
|
|
234
|
+
getChildProcess().execFileSync('git', ['checkout', '--', ...fileArgs], {
|
|
235
|
+
cwd: projectRoot,
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
break;
|
|
239
|
+
case protocol.InboundMessageType.GIT_COMMIT:
|
|
240
|
+
if (commitMessage) {
|
|
241
|
+
getChildProcess().execFileSync('git', ['commit', '-m', commitMessage], {
|
|
242
|
+
cwd: projectRoot,
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
break;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
module.exports = {
|
|
250
|
+
getGitStatus,
|
|
251
|
+
getFileDiff,
|
|
252
|
+
parseDiffStats,
|
|
253
|
+
handleGitAction,
|
|
254
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* dashboard-inbox.js - Inbox Management
|
|
5
|
+
*
|
|
6
|
+
* Manages the inbox of automation results and user actions
|
|
7
|
+
* (accept, dismiss, mark read).
|
|
8
|
+
* Extracted from dashboard-server.js for testability.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Get sorted inbox items (newest first)
|
|
13
|
+
* @param {Map} inbox - Map of itemId -> InboxItem
|
|
14
|
+
* @returns {Array} - Sorted inbox items
|
|
15
|
+
*/
|
|
16
|
+
function getSortedInboxItems(inbox) {
|
|
17
|
+
return Array.from(inbox.values()).sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Handle an inbox action (accept, dismiss, read)
|
|
22
|
+
* @param {Map} inbox - Map of itemId -> InboxItem
|
|
23
|
+
* @param {string} itemId - Item ID
|
|
24
|
+
* @param {string} action - Action to perform (accept, dismiss, read)
|
|
25
|
+
* @returns {{ success: boolean, item?: Object, error?: string }}
|
|
26
|
+
*/
|
|
27
|
+
function handleInboxAction(inbox, itemId, action) {
|
|
28
|
+
const item = inbox.get(itemId);
|
|
29
|
+
if (!item) {
|
|
30
|
+
return { success: false, error: `Inbox item ${itemId} not found` };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
switch (action) {
|
|
34
|
+
case 'accept':
|
|
35
|
+
item.status = 'accepted';
|
|
36
|
+
inbox.delete(itemId);
|
|
37
|
+
return {
|
|
38
|
+
success: true,
|
|
39
|
+
item,
|
|
40
|
+
notification: { level: 'success', message: `Accepted: ${item.title}` },
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
case 'dismiss':
|
|
44
|
+
item.status = 'dismissed';
|
|
45
|
+
inbox.delete(itemId);
|
|
46
|
+
return {
|
|
47
|
+
success: true,
|
|
48
|
+
item,
|
|
49
|
+
notification: { level: 'info', message: `Dismissed: ${item.title}` },
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
case 'read':
|
|
53
|
+
item.status = 'read';
|
|
54
|
+
return { success: true, item };
|
|
55
|
+
|
|
56
|
+
default:
|
|
57
|
+
return { success: false, error: `Unknown action: ${action}` };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
module.exports = {
|
|
62
|
+
getSortedInboxItems,
|
|
63
|
+
handleInboxAction,
|
|
64
|
+
};
|
|
@@ -490,6 +490,7 @@ function createTeamMetrics(traceId, metrics) {
|
|
|
490
490
|
per_agent: (metrics && metrics.per_agent) || {},
|
|
491
491
|
per_gate: (metrics && metrics.per_gate) || {},
|
|
492
492
|
team_completion_ms: metrics && metrics.team_completion_ms,
|
|
493
|
+
total_cost_usd: (metrics && metrics.total_cost_usd) || 0,
|
|
493
494
|
computed_at: metrics && metrics.computed_at,
|
|
494
495
|
timestamp: new Date().toISOString(),
|
|
495
496
|
};
|