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.
Files changed (116) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +58 -86
  3. package/lib/dashboard-automations.js +130 -0
  4. package/lib/dashboard-git.js +254 -0
  5. package/lib/dashboard-inbox.js +64 -0
  6. package/lib/dashboard-protocol.js +1 -0
  7. package/lib/dashboard-server.js +114 -924
  8. package/lib/dashboard-session.js +136 -0
  9. package/lib/dashboard-status.js +72 -0
  10. package/lib/dashboard-terminal.js +354 -0
  11. package/lib/dashboard-websocket.js +88 -0
  12. package/lib/drivers/codex-driver.ts +4 -4
  13. package/lib/feedback.js +9 -2
  14. package/lib/lazy-require.js +59 -0
  15. package/lib/logger.js +106 -0
  16. package/package.json +4 -2
  17. package/scripts/agileflow-configure.js +14 -2
  18. package/scripts/agileflow-welcome.js +450 -459
  19. package/scripts/claude-tmux.sh +113 -5
  20. package/scripts/context-loader.js +4 -9
  21. package/scripts/lib/command-prereqs.js +280 -0
  22. package/scripts/lib/configure-detect.js +92 -2
  23. package/scripts/lib/configure-features.js +411 -1
  24. package/scripts/lib/context-formatter.js +468 -233
  25. package/scripts/lib/context-loader.js +27 -15
  26. package/scripts/lib/damage-control-utils.js +8 -1
  27. package/scripts/lib/feature-catalog.js +321 -0
  28. package/scripts/lib/portable-tasks-cli.js +274 -0
  29. package/scripts/lib/portable-tasks.js +479 -0
  30. package/scripts/lib/signal-detectors.js +1 -1
  31. package/scripts/lib/team-events.js +86 -1
  32. package/scripts/obtain-context.js +28 -4
  33. package/scripts/smart-detect.js +17 -0
  34. package/scripts/strip-ai-attribution.js +63 -0
  35. package/scripts/team-manager.js +90 -0
  36. package/scripts/welcome-deferred.js +437 -0
  37. package/src/core/agents/legal-analyzer-a11y.md +110 -0
  38. package/src/core/agents/legal-analyzer-ai.md +117 -0
  39. package/src/core/agents/legal-analyzer-consumer.md +108 -0
  40. package/src/core/agents/legal-analyzer-content.md +113 -0
  41. package/src/core/agents/legal-analyzer-international.md +115 -0
  42. package/src/core/agents/legal-analyzer-licensing.md +115 -0
  43. package/src/core/agents/legal-analyzer-privacy.md +108 -0
  44. package/src/core/agents/legal-analyzer-security.md +112 -0
  45. package/src/core/agents/legal-analyzer-terms.md +111 -0
  46. package/src/core/agents/legal-consensus.md +242 -0
  47. package/src/core/agents/perf-analyzer-assets.md +174 -0
  48. package/src/core/agents/perf-analyzer-bundle.md +165 -0
  49. package/src/core/agents/perf-analyzer-caching.md +160 -0
  50. package/src/core/agents/perf-analyzer-compute.md +165 -0
  51. package/src/core/agents/perf-analyzer-memory.md +182 -0
  52. package/src/core/agents/perf-analyzer-network.md +157 -0
  53. package/src/core/agents/perf-analyzer-queries.md +155 -0
  54. package/src/core/agents/perf-analyzer-rendering.md +156 -0
  55. package/src/core/agents/perf-consensus.md +280 -0
  56. package/src/core/agents/security-analyzer-api.md +199 -0
  57. package/src/core/agents/security-analyzer-auth.md +160 -0
  58. package/src/core/agents/security-analyzer-authz.md +168 -0
  59. package/src/core/agents/security-analyzer-deps.md +147 -0
  60. package/src/core/agents/security-analyzer-infra.md +176 -0
  61. package/src/core/agents/security-analyzer-injection.md +148 -0
  62. package/src/core/agents/security-analyzer-input.md +191 -0
  63. package/src/core/agents/security-analyzer-secrets.md +175 -0
  64. package/src/core/agents/security-consensus.md +276 -0
  65. package/src/core/agents/team-lead.md +50 -13
  66. package/src/core/agents/test-analyzer-assertions.md +181 -0
  67. package/src/core/agents/test-analyzer-coverage.md +183 -0
  68. package/src/core/agents/test-analyzer-fragility.md +185 -0
  69. package/src/core/agents/test-analyzer-integration.md +155 -0
  70. package/src/core/agents/test-analyzer-maintenance.md +173 -0
  71. package/src/core/agents/test-analyzer-mocking.md +178 -0
  72. package/src/core/agents/test-analyzer-patterns.md +189 -0
  73. package/src/core/agents/test-analyzer-structure.md +177 -0
  74. package/src/core/agents/test-consensus.md +294 -0
  75. package/src/core/commands/audit/legal.md +446 -0
  76. package/src/core/commands/{logic/audit.md → audit/logic.md} +12 -12
  77. package/src/core/commands/audit/performance.md +443 -0
  78. package/src/core/commands/audit/security.md +443 -0
  79. package/src/core/commands/audit/test.md +442 -0
  80. package/src/core/commands/babysit.md +505 -463
  81. package/src/core/commands/configure.md +18 -33
  82. package/src/core/commands/research/ask.md +42 -9
  83. package/src/core/commands/research/import.md +14 -8
  84. package/src/core/commands/research/list.md +17 -16
  85. package/src/core/commands/research/synthesize.md +8 -8
  86. package/src/core/commands/research/view.md +28 -4
  87. package/src/core/commands/team/start.md +36 -7
  88. package/src/core/commands/team/stop.md +5 -2
  89. package/src/core/commands/whats-new.md +2 -2
  90. package/src/core/experts/devops/expertise.yaml +13 -2
  91. package/src/core/experts/documentation/expertise.yaml +26 -4
  92. package/src/core/profiles/COMPARISON.md +170 -0
  93. package/src/core/profiles/README.md +178 -0
  94. package/src/core/profiles/claude-code.yaml +111 -0
  95. package/src/core/profiles/codex.yaml +103 -0
  96. package/src/core/profiles/cursor.yaml +134 -0
  97. package/src/core/profiles/examples.js +250 -0
  98. package/src/core/profiles/loader.js +235 -0
  99. package/src/core/profiles/windsurf.yaml +159 -0
  100. package/src/core/teams/logic-audit.json +6 -0
  101. package/src/core/teams/perf-audit.json +71 -0
  102. package/src/core/teams/security-audit.json +71 -0
  103. package/src/core/teams/test-audit.json +71 -0
  104. package/src/core/templates/command-prerequisites.yaml +169 -0
  105. package/src/core/templates/damage-control-patterns.yaml +9 -0
  106. package/tools/cli/installers/ide/_base-ide.js +33 -3
  107. package/tools/cli/installers/ide/claude-code.js +2 -67
  108. package/tools/cli/installers/ide/codex.js +9 -9
  109. package/tools/cli/installers/ide/cursor.js +165 -4
  110. package/tools/cli/installers/ide/windsurf.js +237 -6
  111. package/tools/cli/lib/content-transformer.js +234 -9
  112. package/tools/cli/lib/docs-setup.js +1 -1
  113. package/tools/cli/lib/ide-generator.js +357 -0
  114. package/tools/cli/lib/ide-registry.js +2 -2
  115. package/scripts/tmux-task-name.sh +0 -75
  116. 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
  [![npm version](https://img.shields.io/npm/v/agileflow?color=brightgreen)](https://www.npmjs.com/package/agileflow)
6
- [![Commands](https://img.shields.io/badge/commands-93-blue)](docs/04-architecture/commands.md)
7
- [![Agents/Experts](https://img.shields.io/badge/agents%2Fexperts-45-orange)](docs/04-architecture/subagents.md)
8
- [![Skills](https://img.shields.io/badge/skills-dynamic-purple)](docs/04-architecture/skills.md)
6
+ [![Commands](https://img.shields.io/badge/commands-98-blue)](https://docs.agileflow.projectquestorg.com/docs/commands)
7
+ [![Agents/Experts](https://img.shields.io/badge/agents%2Fexperts-83-orange)](https://docs.agileflow.projectquestorg.com/docs/agents)
8
+ [![Skills](https://img.shields.io/badge/skills-dynamic-purple)](https://docs.agileflow.projectquestorg.com/docs/features/skills)
9
9
 
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.
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
- That's it! The `npx` command always fetches the latest version.
23
-
24
- **Updates:**
25
- ```bash
26
- npx agileflow@latest update
27
- ```
20
+ The `npx` command always fetches the latest version.
28
21
 
29
- ### After Setup
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/rules/agileflow/` |
35
+ | Cursor | Supported | `.cursor/commands/agileflow/` |
43
36
  | Windsurf | Supported | `.windsurf/workflows/agileflow/` |
44
- | OpenAI Codex CLI | Supported | `.codex/skills/` |
37
+ | OpenAI Codex | Supported | `.codex/skills/` and `~/.codex/prompts/` |
45
38
 
46
39
  ---
47
40
 
48
41
  ## Why AgileFlow?
49
42
 
50
- AgileFlow combines three proven methodologies:
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
- **Key Benefits:**
57
- - Clear priorities and testable increments
58
- - Durable memory and decision history
59
- - Multi-agent collaboration via message bus
60
- - Works with any tech stack or framework
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/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
- | [Skills](docs/04-architecture/skills.md) | Dynamic | Generated on-demand with `/agileflow:skill:create` |
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
- ## Documentation
75
-
76
- Full documentation lives in [`docs/04-architecture/`](docs/04-architecture/):
77
-
78
- ### Reference
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
- - [Skills](docs/04-architecture/skills.md) - Dynamic skill generator with MCP integration
82
-
83
- ### Architecture
84
- - [AgileFlow CLI Overview](docs/04-architecture/agileflow-cli-overview.md) - System architecture
85
- - [Agent Expert System](docs/04-architecture/agent-expert-system.md) - Self-improving agents
86
- - [Multi-Expert Orchestration](docs/04-architecture/multi-expert-orchestration.md) - Parallel expert analysis
87
- - [Multi-Session Coordination](docs/04-architecture/multi-session-coordination.md) - Parallel Claude Code sessions
88
-
89
- ### Configuration
90
- - [Configuration System](docs/04-architecture/configuration-system.md) - 8 configuration agents
91
- - [Hooks System](docs/04-architecture/hooks-system.md) - Event-driven automation
92
- - [Session Harness](docs/04-architecture/session-harness.md) - Test verification and sessions
93
- - [PreCompact Context](docs/04-architecture/precompact-context.md) - Context preservation during compacts
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
- ### Work on a Story
105
- ```bash
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
- ### Use Multi-Expert Analysis
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
- docs/
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
- ## Online Documentation
108
+ ## Documentation
109
+
110
+ Full documentation at **[docs.agileflow.projectquestorg.com](https://docs.agileflow.projectquestorg.com)**.
146
111
 
147
- Visit [docs.agileflow.projectquestorg.com](https://docs.agileflow.projectquestorg.com) for the full documentation site.
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
- For issues or questions, visit the [GitHub repository](https://github.com/projectquestorg/AgileFlow).
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
  };