ruby_reactor 0.5.4 → 0.6.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.
- checksums.yaml +4 -4
- data/.claude/skills/speckit-agent-context-update/SKILL.md +32 -0
- data/.claude/skills/speckit-analyze/SKILL.md +262 -0
- data/.claude/skills/speckit-checklist/SKILL.md +374 -0
- data/.claude/skills/speckit-clarify/SKILL.md +286 -0
- data/.claude/skills/speckit-constitution/SKILL.md +157 -0
- data/.claude/skills/speckit-converge/SKILL.md +277 -0
- data/.claude/skills/speckit-implement/SKILL.md +224 -0
- data/.claude/skills/speckit-plan/SKILL.md +171 -0
- data/.claude/skills/speckit-specify/SKILL.md +346 -0
- data/.claude/skills/speckit-tasks/SKILL.md +215 -0
- data/.claude/skills/speckit-taskstoissues/SKILL.md +110 -0
- data/.release-please-manifest.json +1 -1
- data/.specify/extensions/.registry +19 -0
- data/.specify/extensions/agent-context/README.md +66 -0
- data/.specify/extensions/agent-context/agent-context-config.yml +5 -0
- data/.specify/extensions/agent-context/commands/speckit.agent-context.update.md +27 -0
- data/.specify/extensions/agent-context/extension.yml +34 -0
- data/.specify/extensions/agent-context/scripts/bash/update-agent-context.sh +282 -0
- data/.specify/extensions/agent-context/scripts/powershell/update-agent-context.ps1 +353 -0
- data/.specify/extensions.yml +23 -0
- data/.specify/feature.json +3 -0
- data/.specify/init-options.json +9 -0
- data/.specify/integration.json +15 -0
- data/.specify/integrations/claude.manifest.json +17 -0
- data/.specify/integrations/speckit.manifest.json +17 -0
- data/.specify/memory/constitution.md +134 -0
- data/.specify/scripts/bash/check-prerequisites.sh +189 -0
- data/.specify/scripts/bash/common.sh +619 -0
- data/.specify/scripts/bash/create-new-feature.sh +299 -0
- data/.specify/scripts/bash/setup-plan.sh +84 -0
- data/.specify/scripts/bash/setup-tasks.sh +91 -0
- data/.specify/templates/checklist-template.md +40 -0
- data/.specify/templates/constitution-template.md +50 -0
- data/.specify/templates/plan-template.md +113 -0
- data/.specify/templates/spec-template.md +131 -0
- data/.specify/templates/tasks-template.md +252 -0
- data/.specify/workflows/speckit/workflow.yml +77 -0
- data/.specify/workflows/workflow-registry.json +13 -0
- data/CHANGELOG.md +7 -0
- data/README.md +51 -24
- data/lib/ruby_reactor/adapters/active_job/compat.rb +24 -0
- data/lib/ruby_reactor/adapters/active_job/map_collector_worker.rb +19 -0
- data/lib/ruby_reactor/adapters/active_job/map_element_worker.rb +19 -0
- data/lib/ruby_reactor/adapters/active_job/router.rb +91 -0
- data/lib/ruby_reactor/adapters/active_job/sweeper_worker.rb +16 -0
- data/lib/ruby_reactor/adapters/active_job/worker.rb +24 -0
- data/lib/ruby_reactor/adapters/sidekiq/map_collector_worker.rb +15 -0
- data/lib/ruby_reactor/adapters/sidekiq/map_element_worker.rb +15 -0
- data/lib/ruby_reactor/adapters/sidekiq/router.rb +91 -0
- data/lib/ruby_reactor/adapters/sidekiq/sweeper_worker.rb +19 -0
- data/lib/ruby_reactor/adapters/sidekiq/worker.rb +25 -0
- data/lib/ruby_reactor/configuration.rb +24 -4
- data/lib/ruby_reactor/map/element_executor.rb +1 -1
- data/lib/ruby_reactor/rspec/active_job_helpers.rb +52 -0
- data/lib/ruby_reactor/rspec/async_test_helpers.rb +41 -0
- data/lib/ruby_reactor/rspec/sidekiq_helpers.rb +3 -3
- data/lib/ruby_reactor/rspec/test_subject.rb +11 -7
- data/lib/ruby_reactor/rspec.rb +4 -0
- data/lib/ruby_reactor/sweeper_job.rb +70 -0
- data/lib/ruby_reactor/version.rb +1 -1
- data/lib/ruby_reactor/worker.rb +226 -0
- data/lib/ruby_reactor.rb +40 -1
- data/specs/active_job.md +259 -0
- metadata +54 -6
- data/lib/ruby_reactor/sidekiq_adapter.rb +0 -87
- data/lib/ruby_reactor/sidekiq_workers/map_collector_worker.rb +0 -13
- data/lib/ruby_reactor/sidekiq_workers/map_element_worker.rb +0 -13
- data/lib/ruby_reactor/sidekiq_workers/sweeper_worker.rb +0 -73
- data/lib/ruby_reactor/sidekiq_workers/worker.rb +0 -222
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# Feature Specification: [FEATURE NAME]
|
|
2
|
+
|
|
3
|
+
**Feature Branch**: `[###-feature-name]`
|
|
4
|
+
|
|
5
|
+
**Created**: [DATE]
|
|
6
|
+
|
|
7
|
+
**Status**: Draft
|
|
8
|
+
|
|
9
|
+
**Input**: User description: "$ARGUMENTS"
|
|
10
|
+
|
|
11
|
+
## User Scenarios & Testing *(mandatory)*
|
|
12
|
+
|
|
13
|
+
<!--
|
|
14
|
+
IMPORTANT: User stories should be PRIORITIZED as user journeys ordered by importance.
|
|
15
|
+
Each user story/journey must be INDEPENDENTLY TESTABLE - meaning if you implement just ONE of them,
|
|
16
|
+
you should still have a viable MVP (Minimum Viable Product) that delivers value.
|
|
17
|
+
|
|
18
|
+
Assign priorities (P1, P2, P3, etc.) to each story, where P1 is the most critical.
|
|
19
|
+
Think of each story as a standalone slice of functionality that can be:
|
|
20
|
+
- Developed independently
|
|
21
|
+
- Tested independently
|
|
22
|
+
- Deployed independently
|
|
23
|
+
- Demonstrated to users independently
|
|
24
|
+
-->
|
|
25
|
+
|
|
26
|
+
### User Story 1 - [Brief Title] (Priority: P1)
|
|
27
|
+
|
|
28
|
+
[Describe this user journey in plain language]
|
|
29
|
+
|
|
30
|
+
**Why this priority**: [Explain the value and why it has this priority level]
|
|
31
|
+
|
|
32
|
+
**Independent Test**: [Describe how this can be tested independently - e.g., "Can be fully tested by [specific action] and delivers [specific value]"]
|
|
33
|
+
|
|
34
|
+
**Acceptance Scenarios**:
|
|
35
|
+
|
|
36
|
+
1. **Given** [initial state], **When** [action], **Then** [expected outcome]
|
|
37
|
+
2. **Given** [initial state], **When** [action], **Then** [expected outcome]
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
### User Story 2 - [Brief Title] (Priority: P2)
|
|
42
|
+
|
|
43
|
+
[Describe this user journey in plain language]
|
|
44
|
+
|
|
45
|
+
**Why this priority**: [Explain the value and why it has this priority level]
|
|
46
|
+
|
|
47
|
+
**Independent Test**: [Describe how this can be tested independently]
|
|
48
|
+
|
|
49
|
+
**Acceptance Scenarios**:
|
|
50
|
+
|
|
51
|
+
1. **Given** [initial state], **When** [action], **Then** [expected outcome]
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
### User Story 3 - [Brief Title] (Priority: P3)
|
|
56
|
+
|
|
57
|
+
[Describe this user journey in plain language]
|
|
58
|
+
|
|
59
|
+
**Why this priority**: [Explain the value and why it has this priority level]
|
|
60
|
+
|
|
61
|
+
**Independent Test**: [Describe how this can be tested independently]
|
|
62
|
+
|
|
63
|
+
**Acceptance Scenarios**:
|
|
64
|
+
|
|
65
|
+
1. **Given** [initial state], **When** [action], **Then** [expected outcome]
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
[Add more user stories as needed, each with an assigned priority]
|
|
70
|
+
|
|
71
|
+
### Edge Cases
|
|
72
|
+
|
|
73
|
+
<!--
|
|
74
|
+
ACTION REQUIRED: The content in this section represents placeholders.
|
|
75
|
+
Fill them out with the right edge cases.
|
|
76
|
+
-->
|
|
77
|
+
|
|
78
|
+
- What happens when [boundary condition]?
|
|
79
|
+
- How does system handle [error scenario]?
|
|
80
|
+
|
|
81
|
+
## Requirements *(mandatory)*
|
|
82
|
+
|
|
83
|
+
<!--
|
|
84
|
+
ACTION REQUIRED: The content in this section represents placeholders.
|
|
85
|
+
Fill them out with the right functional requirements.
|
|
86
|
+
-->
|
|
87
|
+
|
|
88
|
+
### Functional Requirements
|
|
89
|
+
|
|
90
|
+
- **FR-001**: System MUST [specific capability, e.g., "allow users to create accounts"]
|
|
91
|
+
- **FR-002**: System MUST [specific capability, e.g., "validate email addresses"]
|
|
92
|
+
- **FR-003**: Users MUST be able to [key interaction, e.g., "reset their password"]
|
|
93
|
+
- **FR-004**: System MUST [data requirement, e.g., "persist user preferences"]
|
|
94
|
+
- **FR-005**: System MUST [behavior, e.g., "log all security events"]
|
|
95
|
+
|
|
96
|
+
*Example of marking unclear requirements:*
|
|
97
|
+
|
|
98
|
+
- **FR-006**: System MUST authenticate users via [NEEDS CLARIFICATION: auth method not specified - email/password, SSO, OAuth?]
|
|
99
|
+
- **FR-007**: System MUST retain user data for [NEEDS CLARIFICATION: retention period not specified]
|
|
100
|
+
|
|
101
|
+
### Key Entities *(include if feature involves data)*
|
|
102
|
+
|
|
103
|
+
- **[Entity 1]**: [What it represents, key attributes without implementation]
|
|
104
|
+
- **[Entity 2]**: [What it represents, relationships to other entities]
|
|
105
|
+
|
|
106
|
+
## Success Criteria *(mandatory)*
|
|
107
|
+
|
|
108
|
+
<!--
|
|
109
|
+
ACTION REQUIRED: Define measurable success criteria.
|
|
110
|
+
These must be technology-agnostic and measurable.
|
|
111
|
+
-->
|
|
112
|
+
|
|
113
|
+
### Measurable Outcomes
|
|
114
|
+
|
|
115
|
+
- **SC-001**: [Measurable metric, e.g., "Users can complete account creation in under 2 minutes"]
|
|
116
|
+
- **SC-002**: [Measurable metric, e.g., "System handles 1000 concurrent users without degradation"]
|
|
117
|
+
- **SC-003**: [User satisfaction metric, e.g., "90% of users successfully complete primary task on first attempt"]
|
|
118
|
+
- **SC-004**: [Business metric, e.g., "Reduce support tickets related to [X] by 50%"]
|
|
119
|
+
|
|
120
|
+
## Assumptions
|
|
121
|
+
|
|
122
|
+
<!--
|
|
123
|
+
ACTION REQUIRED: The content in this section represents placeholders.
|
|
124
|
+
Fill them out with the right assumptions based on reasonable defaults
|
|
125
|
+
chosen when the feature description did not specify certain details.
|
|
126
|
+
-->
|
|
127
|
+
|
|
128
|
+
- [Assumption about target users, e.g., "Users have stable internet connectivity"]
|
|
129
|
+
- [Assumption about scope boundaries, e.g., "Mobile support is out of scope for v1"]
|
|
130
|
+
- [Assumption about data/environment, e.g., "Existing authentication system will be reused"]
|
|
131
|
+
- [Dependency on existing system/service, e.g., "Requires access to the existing user profile API"]
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
---
|
|
2
|
+
|
|
3
|
+
description: "Task list template for feature implementation"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Tasks: [FEATURE NAME]
|
|
7
|
+
|
|
8
|
+
**Input**: Design documents from `/specs/[###-feature-name]/`
|
|
9
|
+
|
|
10
|
+
**Prerequisites**: plan.md (required), spec.md (required for user stories), research.md, data-model.md, contracts/
|
|
11
|
+
|
|
12
|
+
**Tests**: The examples below include test tasks. Tests are OPTIONAL - only include them if explicitly requested in the feature specification.
|
|
13
|
+
|
|
14
|
+
**Organization**: Tasks are grouped by user story to enable independent implementation and testing of each story.
|
|
15
|
+
|
|
16
|
+
## Format: `[ID] [P?] [Story] Description`
|
|
17
|
+
|
|
18
|
+
- **[P]**: Can run in parallel (different files, no dependencies)
|
|
19
|
+
- **[Story]**: Which user story this task belongs to (e.g., US1, US2, US3)
|
|
20
|
+
- Include exact file paths in descriptions
|
|
21
|
+
|
|
22
|
+
## Path Conventions
|
|
23
|
+
|
|
24
|
+
- **Single project**: `src/`, `tests/` at repository root
|
|
25
|
+
- **Web app**: `backend/src/`, `frontend/src/`
|
|
26
|
+
- **Mobile**: `api/src/`, `ios/src/` or `android/src/`
|
|
27
|
+
- Paths shown below assume single project - adjust based on plan.md structure
|
|
28
|
+
|
|
29
|
+
<!--
|
|
30
|
+
============================================================================
|
|
31
|
+
IMPORTANT: The tasks below are SAMPLE TASKS for illustration purposes only.
|
|
32
|
+
|
|
33
|
+
The /speckit-tasks command MUST replace these with actual tasks based on:
|
|
34
|
+
- User stories from spec.md (with their priorities P1, P2, P3...)
|
|
35
|
+
- Feature requirements from plan.md
|
|
36
|
+
- Entities from data-model.md
|
|
37
|
+
- Endpoints from contracts/
|
|
38
|
+
|
|
39
|
+
Tasks MUST be organized by user story so each story can be:
|
|
40
|
+
- Implemented independently
|
|
41
|
+
- Tested independently
|
|
42
|
+
- Delivered as an MVP increment
|
|
43
|
+
|
|
44
|
+
DO NOT keep these sample tasks in the generated tasks.md file.
|
|
45
|
+
============================================================================
|
|
46
|
+
-->
|
|
47
|
+
|
|
48
|
+
## Phase 1: Setup (Shared Infrastructure)
|
|
49
|
+
|
|
50
|
+
**Purpose**: Project initialization and basic structure
|
|
51
|
+
|
|
52
|
+
- [ ] T001 Create project structure per implementation plan
|
|
53
|
+
- [ ] T002 Initialize [language] project with [framework] dependencies
|
|
54
|
+
- [ ] T003 [P] Configure linting and formatting tools
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Phase 2: Foundational (Blocking Prerequisites)
|
|
59
|
+
|
|
60
|
+
**Purpose**: Core infrastructure that MUST be complete before ANY user story can be implemented
|
|
61
|
+
|
|
62
|
+
**⚠️ CRITICAL**: No user story work can begin until this phase is complete
|
|
63
|
+
|
|
64
|
+
Examples of foundational tasks (adjust based on your project):
|
|
65
|
+
|
|
66
|
+
- [ ] T004 Setup database schema and migrations framework
|
|
67
|
+
- [ ] T005 [P] Implement authentication/authorization framework
|
|
68
|
+
- [ ] T006 [P] Setup API routing and middleware structure
|
|
69
|
+
- [ ] T007 Create base models/entities that all stories depend on
|
|
70
|
+
- [ ] T008 Configure error handling and logging infrastructure
|
|
71
|
+
- [ ] T009 Setup environment configuration management
|
|
72
|
+
|
|
73
|
+
**Checkpoint**: Foundation ready - user story implementation can now begin in parallel
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Phase 3: User Story 1 - [Title] (Priority: P1) 🎯 MVP
|
|
78
|
+
|
|
79
|
+
**Goal**: [Brief description of what this story delivers]
|
|
80
|
+
|
|
81
|
+
**Independent Test**: [How to verify this story works on its own]
|
|
82
|
+
|
|
83
|
+
### Tests for User Story 1 (OPTIONAL - only if tests requested) ⚠️
|
|
84
|
+
|
|
85
|
+
> **NOTE: Write these tests FIRST, ensure they FAIL before implementation**
|
|
86
|
+
|
|
87
|
+
- [ ] T010 [P] [US1] Contract test for [endpoint] in tests/contract/test_[name].py
|
|
88
|
+
- [ ] T011 [P] [US1] Integration test for [user journey] in tests/integration/test_[name].py
|
|
89
|
+
|
|
90
|
+
### Implementation for User Story 1
|
|
91
|
+
|
|
92
|
+
- [ ] T012 [P] [US1] Create [Entity1] model in src/models/[entity1].py
|
|
93
|
+
- [ ] T013 [P] [US1] Create [Entity2] model in src/models/[entity2].py
|
|
94
|
+
- [ ] T014 [US1] Implement [Service] in src/services/[service].py (depends on T012, T013)
|
|
95
|
+
- [ ] T015 [US1] Implement [endpoint/feature] in src/[location]/[file].py
|
|
96
|
+
- [ ] T016 [US1] Add validation and error handling
|
|
97
|
+
- [ ] T017 [US1] Add logging for user story 1 operations
|
|
98
|
+
|
|
99
|
+
**Checkpoint**: At this point, User Story 1 should be fully functional and testable independently
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Phase 4: User Story 2 - [Title] (Priority: P2)
|
|
104
|
+
|
|
105
|
+
**Goal**: [Brief description of what this story delivers]
|
|
106
|
+
|
|
107
|
+
**Independent Test**: [How to verify this story works on its own]
|
|
108
|
+
|
|
109
|
+
### Tests for User Story 2 (OPTIONAL - only if tests requested) ⚠️
|
|
110
|
+
|
|
111
|
+
- [ ] T018 [P] [US2] Contract test for [endpoint] in tests/contract/test_[name].py
|
|
112
|
+
- [ ] T019 [P] [US2] Integration test for [user journey] in tests/integration/test_[name].py
|
|
113
|
+
|
|
114
|
+
### Implementation for User Story 2
|
|
115
|
+
|
|
116
|
+
- [ ] T020 [P] [US2] Create [Entity] model in src/models/[entity].py
|
|
117
|
+
- [ ] T021 [US2] Implement [Service] in src/services/[service].py
|
|
118
|
+
- [ ] T022 [US2] Implement [endpoint/feature] in src/[location]/[file].py
|
|
119
|
+
- [ ] T023 [US2] Integrate with User Story 1 components (if needed)
|
|
120
|
+
|
|
121
|
+
**Checkpoint**: At this point, User Stories 1 AND 2 should both work independently
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Phase 5: User Story 3 - [Title] (Priority: P3)
|
|
126
|
+
|
|
127
|
+
**Goal**: [Brief description of what this story delivers]
|
|
128
|
+
|
|
129
|
+
**Independent Test**: [How to verify this story works on its own]
|
|
130
|
+
|
|
131
|
+
### Tests for User Story 3 (OPTIONAL - only if tests requested) ⚠️
|
|
132
|
+
|
|
133
|
+
- [ ] T024 [P] [US3] Contract test for [endpoint] in tests/contract/test_[name].py
|
|
134
|
+
- [ ] T025 [P] [US3] Integration test for [user journey] in tests/integration/test_[name].py
|
|
135
|
+
|
|
136
|
+
### Implementation for User Story 3
|
|
137
|
+
|
|
138
|
+
- [ ] T026 [P] [US3] Create [Entity] model in src/models/[entity].py
|
|
139
|
+
- [ ] T027 [US3] Implement [Service] in src/services/[service].py
|
|
140
|
+
- [ ] T028 [US3] Implement [endpoint/feature] in src/[location]/[file].py
|
|
141
|
+
|
|
142
|
+
**Checkpoint**: All user stories should now be independently functional
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
[Add more user story phases as needed, following the same pattern]
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## Phase N: Polish & Cross-Cutting Concerns
|
|
151
|
+
|
|
152
|
+
**Purpose**: Improvements that affect multiple user stories
|
|
153
|
+
|
|
154
|
+
- [ ] TXXX [P] Documentation updates in docs/
|
|
155
|
+
- [ ] TXXX Code cleanup and refactoring
|
|
156
|
+
- [ ] TXXX Performance optimization across all stories
|
|
157
|
+
- [ ] TXXX [P] Additional unit tests (if requested) in tests/unit/
|
|
158
|
+
- [ ] TXXX Security hardening
|
|
159
|
+
- [ ] TXXX Run quickstart.md validation
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## Dependencies & Execution Order
|
|
164
|
+
|
|
165
|
+
### Phase Dependencies
|
|
166
|
+
|
|
167
|
+
- **Setup (Phase 1)**: No dependencies - can start immediately
|
|
168
|
+
- **Foundational (Phase 2)**: Depends on Setup completion - BLOCKS all user stories
|
|
169
|
+
- **User Stories (Phase 3+)**: All depend on Foundational phase completion
|
|
170
|
+
- User stories can then proceed in parallel (if staffed)
|
|
171
|
+
- Or sequentially in priority order (P1 → P2 → P3)
|
|
172
|
+
- **Polish (Final Phase)**: Depends on all desired user stories being complete
|
|
173
|
+
|
|
174
|
+
### User Story Dependencies
|
|
175
|
+
|
|
176
|
+
- **User Story 1 (P1)**: Can start after Foundational (Phase 2) - No dependencies on other stories
|
|
177
|
+
- **User Story 2 (P2)**: Can start after Foundational (Phase 2) - May integrate with US1 but should be independently testable
|
|
178
|
+
- **User Story 3 (P3)**: Can start after Foundational (Phase 2) - May integrate with US1/US2 but should be independently testable
|
|
179
|
+
|
|
180
|
+
### Within Each User Story
|
|
181
|
+
|
|
182
|
+
- Tests (if included) MUST be written and FAIL before implementation
|
|
183
|
+
- Models before services
|
|
184
|
+
- Services before endpoints
|
|
185
|
+
- Core implementation before integration
|
|
186
|
+
- Story complete before moving to next priority
|
|
187
|
+
|
|
188
|
+
### Parallel Opportunities
|
|
189
|
+
|
|
190
|
+
- All Setup tasks marked [P] can run in parallel
|
|
191
|
+
- All Foundational tasks marked [P] can run in parallel (within Phase 2)
|
|
192
|
+
- Once Foundational phase completes, all user stories can start in parallel (if team capacity allows)
|
|
193
|
+
- All tests for a user story marked [P] can run in parallel
|
|
194
|
+
- Models within a story marked [P] can run in parallel
|
|
195
|
+
- Different user stories can be worked on in parallel by different team members
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## Parallel Example: User Story 1
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
# Launch all tests for User Story 1 together (if tests requested):
|
|
203
|
+
Task: "Contract test for [endpoint] in tests/contract/test_[name].py"
|
|
204
|
+
Task: "Integration test for [user journey] in tests/integration/test_[name].py"
|
|
205
|
+
|
|
206
|
+
# Launch all models for User Story 1 together:
|
|
207
|
+
Task: "Create [Entity1] model in src/models/[entity1].py"
|
|
208
|
+
Task: "Create [Entity2] model in src/models/[entity2].py"
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## Implementation Strategy
|
|
214
|
+
|
|
215
|
+
### MVP First (User Story 1 Only)
|
|
216
|
+
|
|
217
|
+
1. Complete Phase 1: Setup
|
|
218
|
+
2. Complete Phase 2: Foundational (CRITICAL - blocks all stories)
|
|
219
|
+
3. Complete Phase 3: User Story 1
|
|
220
|
+
4. **STOP and VALIDATE**: Test User Story 1 independently
|
|
221
|
+
5. Deploy/demo if ready
|
|
222
|
+
|
|
223
|
+
### Incremental Delivery
|
|
224
|
+
|
|
225
|
+
1. Complete Setup + Foundational → Foundation ready
|
|
226
|
+
2. Add User Story 1 → Test independently → Deploy/Demo (MVP!)
|
|
227
|
+
3. Add User Story 2 → Test independently → Deploy/Demo
|
|
228
|
+
4. Add User Story 3 → Test independently → Deploy/Demo
|
|
229
|
+
5. Each story adds value without breaking previous stories
|
|
230
|
+
|
|
231
|
+
### Parallel Team Strategy
|
|
232
|
+
|
|
233
|
+
With multiple developers:
|
|
234
|
+
|
|
235
|
+
1. Team completes Setup + Foundational together
|
|
236
|
+
2. Once Foundational is done:
|
|
237
|
+
- Developer A: User Story 1
|
|
238
|
+
- Developer B: User Story 2
|
|
239
|
+
- Developer C: User Story 3
|
|
240
|
+
3. Stories complete and integrate independently
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
## Notes
|
|
245
|
+
|
|
246
|
+
- [P] tasks = different files, no dependencies
|
|
247
|
+
- [Story] label maps task to specific user story for traceability
|
|
248
|
+
- Each user story should be independently completable and testable
|
|
249
|
+
- Verify tests fail before implementing
|
|
250
|
+
- Commit after each task or logical group
|
|
251
|
+
- Stop at any checkpoint to validate story independently
|
|
252
|
+
- Avoid: vague tasks, same file conflicts, cross-story dependencies that break independence
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
schema_version: "1.0"
|
|
2
|
+
workflow:
|
|
3
|
+
id: "speckit"
|
|
4
|
+
name: "Full SDD Cycle"
|
|
5
|
+
version: "1.0.0"
|
|
6
|
+
author: "GitHub"
|
|
7
|
+
description: "Runs specify → plan → tasks → implement with review gates"
|
|
8
|
+
|
|
9
|
+
requires:
|
|
10
|
+
# 0.8.5 is the first release with engine-side resolution of the
|
|
11
|
+
# ``integration: "auto"`` default. Older versions would treat "auto"
|
|
12
|
+
# as a literal integration key and fail at dispatch.
|
|
13
|
+
speckit_version: ">=0.8.5"
|
|
14
|
+
integrations:
|
|
15
|
+
# The four commands below (specify, plan, tasks, implement) are core
|
|
16
|
+
# spec-kit commands provided by every integration. The list here is an
|
|
17
|
+
# advisory, non-exhaustive compatibility hint following the documented
|
|
18
|
+
# ``any: [...]`` schema -- it is NOT a closed set. The workflow runs
|
|
19
|
+
# against any integration the project was initialized with, including
|
|
20
|
+
# ones not listed below, as long as that integration provides the four
|
|
21
|
+
# core commands referenced in ``steps``.
|
|
22
|
+
any:
|
|
23
|
+
- "claude"
|
|
24
|
+
- "copilot"
|
|
25
|
+
- "gemini"
|
|
26
|
+
- "opencode"
|
|
27
|
+
|
|
28
|
+
inputs:
|
|
29
|
+
spec:
|
|
30
|
+
type: string
|
|
31
|
+
required: true
|
|
32
|
+
prompt: "Describe what you want to build"
|
|
33
|
+
integration:
|
|
34
|
+
type: string
|
|
35
|
+
default: "auto"
|
|
36
|
+
prompt: "Integration to use (e.g. claude, copilot, gemini; 'auto' uses the project's initialized integration)"
|
|
37
|
+
scope:
|
|
38
|
+
type: string
|
|
39
|
+
default: "full"
|
|
40
|
+
enum: ["full", "backend-only", "frontend-only"]
|
|
41
|
+
|
|
42
|
+
steps:
|
|
43
|
+
- id: specify
|
|
44
|
+
command: speckit.specify
|
|
45
|
+
integration: "{{ inputs.integration }}"
|
|
46
|
+
input:
|
|
47
|
+
args: "{{ inputs.spec }}"
|
|
48
|
+
|
|
49
|
+
- id: review-spec
|
|
50
|
+
type: gate
|
|
51
|
+
message: "Review the generated spec before planning."
|
|
52
|
+
options: [approve, reject]
|
|
53
|
+
on_reject: abort
|
|
54
|
+
|
|
55
|
+
- id: plan
|
|
56
|
+
command: speckit.plan
|
|
57
|
+
integration: "{{ inputs.integration }}"
|
|
58
|
+
input:
|
|
59
|
+
args: "{{ inputs.spec }}"
|
|
60
|
+
|
|
61
|
+
- id: review-plan
|
|
62
|
+
type: gate
|
|
63
|
+
message: "Review the plan before generating tasks."
|
|
64
|
+
options: [approve, reject]
|
|
65
|
+
on_reject: abort
|
|
66
|
+
|
|
67
|
+
- id: tasks
|
|
68
|
+
command: speckit.tasks
|
|
69
|
+
integration: "{{ inputs.integration }}"
|
|
70
|
+
input:
|
|
71
|
+
args: "{{ inputs.spec }}"
|
|
72
|
+
|
|
73
|
+
- id: implement
|
|
74
|
+
command: speckit.implement
|
|
75
|
+
integration: "{{ inputs.integration }}"
|
|
76
|
+
input:
|
|
77
|
+
args: "{{ inputs.spec }}"
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema_version": "1.0",
|
|
3
|
+
"workflows": {
|
|
4
|
+
"speckit": {
|
|
5
|
+
"name": "Full SDD Cycle",
|
|
6
|
+
"version": "1.0.0",
|
|
7
|
+
"description": "Runs specify \u2192 plan \u2192 tasks \u2192 implement with review gates",
|
|
8
|
+
"source": "bundled",
|
|
9
|
+
"installed_at": "2026-06-23T23:29:12.184843+00:00",
|
|
10
|
+
"updated_at": "2026-06-23T23:29:12.184848+00:00"
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.6.0](https://github.com/arturictus/ruby_reactor/compare/v0.5.4...v0.6.0) (2026-08-16)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* ActiveJob Support ([#42](https://github.com/arturictus/ruby_reactor/issues/42)) ([0fb6dc4](https://github.com/arturictus/ruby_reactor/commit/0fb6dc4ae4b16c34e0aa33a66f95df3e14ae0807))
|
|
9
|
+
|
|
3
10
|
## [0.5.4](https://github.com/arturictus/ruby_reactor/compare/v0.5.3...v0.5.4) (2026-06-18)
|
|
4
11
|
|
|
5
12
|
|
data/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
# RubyReactor
|
|
7
7
|
|
|
8
|
-
A dynamic, dependency-resolving saga orchestrator for Ruby. Ruby Reactor implements the Saga pattern with compensation-based error handling and DAG-based execution planning. It leverages **Sidekiq** for asynchronous execution and **Redis** for state persistence.
|
|
8
|
+
A dynamic, dependency-resolving saga orchestrator for Ruby. Ruby Reactor implements the Saga pattern with compensation-based error handling and DAG-based execution planning. It leverages **Sidekiq or ActiveJob** for asynchronous execution and **Redis** for state persistence.
|
|
9
9
|
|
|
10
10
|

|
|
11
11
|
|
|
@@ -18,7 +18,7 @@ The key value is **Reliability**: if any part of your workflow fails, Ruby React
|
|
|
18
18
|
## Features
|
|
19
19
|
|
|
20
20
|
- **DAG-based Execution**: Steps are executed based on their dependencies, allowing for parallel execution of independent steps.
|
|
21
|
-
- **Async Execution**: Steps can be executed asynchronously in the background using Sidekiq.
|
|
21
|
+
- **Async Execution**: Steps can be executed asynchronously in the background using Sidekiq or ActiveJob (so any ActiveJob-compatible queue — Resque, Solid Queue, GoodJob, etc. — works too).
|
|
22
22
|
- **Map & Parallel Execution**: Iterate over collections in parallel with the `map` step, distributing work across multiple workers.
|
|
23
23
|
- **Retries**: Configurable retry logic for failed steps, with exponential backoff.
|
|
24
24
|
- **Compensation**: Automatic rollback of completed steps when a failure occurs.
|
|
@@ -35,7 +35,7 @@ The key value is **Reliability**: if any part of your workflow fails, Ruby React
|
|
|
35
35
|
| Interrupts (pause/resume)| Yes | No | No | Manual |
|
|
36
36
|
| Locks / sem / rate / per | Yes | No | No | Manual |
|
|
37
37
|
| Built-in web dashboard | Yes | No | No | No |
|
|
38
|
-
| Async
|
|
38
|
+
| Async (Sidekiq/AJ) | Yes | No | Limited | Yes |
|
|
39
39
|
| Durable crash recovery | Yes | No | No | Manual |
|
|
40
40
|
|
|
41
41
|
## Real-World Use Cases
|
|
@@ -115,18 +115,21 @@ RubyReactor.configure do |config|
|
|
|
115
115
|
## Extra options passed to Redis.new. Default: {}.
|
|
116
116
|
# config.storage.redis_options = { timeout: 1 }
|
|
117
117
|
|
|
118
|
-
## === Sidekiq ===
|
|
118
|
+
## === Background job backend (Sidekiq by default, or ActiveJob) ===
|
|
119
119
|
|
|
120
|
-
##
|
|
121
|
-
# config.
|
|
120
|
+
## Queue used by RubyReactor's async worker. Default: :default.
|
|
121
|
+
# config.queue_name = :default
|
|
122
122
|
|
|
123
|
-
##
|
|
124
|
-
##
|
|
125
|
-
# config.
|
|
123
|
+
## Retry count for infrastructure failures only (deserialization, Redis,
|
|
124
|
+
## network). Step retries are managed separately. Default: 3.
|
|
125
|
+
# config.job_retry_count = 3
|
|
126
|
+
|
|
127
|
+
## `sidekiq_queue` / `sidekiq_retry_count` still work as deprecated aliases
|
|
128
|
+
## for `queue_name` / `job_retry_count` above.
|
|
126
129
|
|
|
127
130
|
## === Contention snooze (locks / semaphores / rate limits / ordered locks) ===
|
|
128
131
|
|
|
129
|
-
## When a
|
|
132
|
+
## When a worker cannot acquire a primitive it re-enqueues itself with
|
|
130
133
|
## `lock_snooze_base_delay + rand(0..lock_snooze_jitter)` seconds (rate-limit
|
|
131
134
|
## uses a precise `retry_after_seconds` hint from the error; ordered-lock waits
|
|
132
135
|
## re-poll at the base delay so a successor catches its blocker finishing fast),
|
|
@@ -162,10 +165,11 @@ RubyReactor.configure do |config|
|
|
|
162
165
|
## Logger. Default: Logger.new($stdout).
|
|
163
166
|
# config.logger = Logger.new($stdout)
|
|
164
167
|
|
|
165
|
-
## Async router. Default: RubyReactor::
|
|
166
|
-
##
|
|
168
|
+
## Async router. Default: RubyReactor::Adapters::Sidekiq::Router. Swap in the
|
|
169
|
+
## built-in ActiveJob adapter (see "Async Execution" below), or point at any
|
|
170
|
+
## custom adapter — it only needs to respond to
|
|
167
171
|
## `perform_async(context_id, reactor_class_name, **)`.
|
|
168
|
-
# config.async_router =
|
|
172
|
+
# config.async_router = RubyReactor::Adapters::ActiveJob::Router
|
|
169
173
|
|
|
170
174
|
## === Examples (no default — set these to use the feature) ===
|
|
171
175
|
|
|
@@ -181,8 +185,9 @@ You can also leave out the `configure` block entirely — defaults work for loca
|
|
|
181
185
|
|
|
182
186
|
> **Crash recovery needs a kick.** The `sweeper_*` settings above only configure
|
|
183
187
|
> the recovery sweeper — they do not start it. Call `RubyReactor.start_sweeper!`
|
|
184
|
-
> once at boot (ideally from a
|
|
185
|
-
>
|
|
188
|
+
> once at boot (ideally from a worker-process startup hook — a Sidekiq
|
|
189
|
+
> `on(:startup)` hook, or a Rails initializer for ActiveJob) or no crashed
|
|
190
|
+
> reactor will ever resume. See [Durability & Recovery](#durability--recovery).
|
|
186
191
|
|
|
187
192
|
|
|
188
193
|
## Quick Start
|
|
@@ -380,7 +385,18 @@ end
|
|
|
380
385
|
|
|
381
386
|
### Async Execution
|
|
382
387
|
|
|
383
|
-
Execute reactors in the background using Sidekiq.
|
|
388
|
+
Execute reactors in the background using Sidekiq or ActiveJob. The backend is
|
|
389
|
+
chosen via `config.async_router` (defaults to the Sidekiq adapter); to run on
|
|
390
|
+
ActiveJob instead:
|
|
391
|
+
|
|
392
|
+
```ruby
|
|
393
|
+
RubyReactor.configure do |config|
|
|
394
|
+
config.async_router = RubyReactor::Adapters::ActiveJob::Router
|
|
395
|
+
end
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
That's the only switch — everything below (full-reactor async, step-level
|
|
399
|
+
async, durability, retries, snoozing) works identically on either backend.
|
|
384
400
|
|
|
385
401
|
#### Full Reactor Async
|
|
386
402
|
|
|
@@ -455,8 +471,8 @@ reactor only resumes when the recovery sweeper notices the lapsed liveness lock
|
|
|
455
471
|
and re-enqueues it. The sweeper is a self-rescheduling chain — **kick it once per
|
|
456
472
|
process boot:**
|
|
457
473
|
|
|
458
|
-
The recommended spot is a
|
|
459
|
-
process runs recovery (not your web/console/client processes):
|
|
474
|
+
The recommended spot is a worker-process startup hook, so only the worker
|
|
475
|
+
process runs recovery (not your web/console/client processes). On Sidekiq:
|
|
460
476
|
|
|
461
477
|
```ruby
|
|
462
478
|
# config/initializers/sidekiq.rb
|
|
@@ -465,7 +481,18 @@ Sidekiq.configure_server do |config|
|
|
|
465
481
|
end
|
|
466
482
|
```
|
|
467
483
|
|
|
468
|
-
|
|
484
|
+
ActiveJob has no equivalent server-only hook — call it from wherever your
|
|
485
|
+
queue adapter's worker process boots (e.g. `bin/jobs` for Solid Queue, or a
|
|
486
|
+
dedicated initializer guarded by an env var so it doesn't also run in web
|
|
487
|
+
processes):
|
|
488
|
+
|
|
489
|
+
```ruby
|
|
490
|
+
# config/initializers/ruby_reactor.rb
|
|
491
|
+
RubyReactor.start_sweeper! if ENV["RUBY_REACTOR_WORKER"]
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
Anywhere that runs once at boot works too (idempotent, so it's safe even if
|
|
495
|
+
every process calls it) — e.g. an unconditional Rails initializer:
|
|
469
496
|
|
|
470
497
|
```ruby
|
|
471
498
|
# config/initializers/ruby_reactor.rb
|
|
@@ -599,7 +626,7 @@ class ChargeReactor < RubyReactor::Reactor
|
|
|
599
626
|
|
|
600
627
|
# Respect upstream Stripe rate limits: 3/sec and 100/min.
|
|
601
628
|
# Async workers snooze for exactly retry_after seconds instead of
|
|
602
|
-
# consuming
|
|
629
|
+
# consuming the backend's retry budget.
|
|
603
630
|
with_rate_limit(
|
|
604
631
|
limits: { second: 3, minute: 100 }
|
|
605
632
|
) { |inputs| "stripe:#{inputs[:account_id]}" }
|
|
@@ -657,7 +684,7 @@ Referencing an unregistered name raises `RubyReactor::RateLimitRegistry::Unknown
|
|
|
657
684
|
On contention:
|
|
658
685
|
|
|
659
686
|
- **Inline** (`Reactor.run`) raises `RubyReactor::Lock::AcquisitionError` / `RubyReactor::Semaphore::AcquisitionError` / `RubyReactor::RateLimit::ExceededError` / `RubyReactor::OrderedLock::WaitError`.
|
|
660
|
-
- **Async** (Sidekiq) snoozes the job via `perform_in(delay, ...)`. For rate limits the delay uses the error's `retry_after_seconds` hint (precise wakeup — the bucket roll time is known exactly); for locks, semaphores, and ordered-lock waits it's `lock_snooze_base_delay + jitter` (a short re-poll, since a held lock or a live blocker nonce typically clears in milliseconds). Snoozes do not count against the
|
|
687
|
+
- **Async** (Sidekiq or ActiveJob) snoozes the job via `perform_in(delay, ...)`. For rate limits the delay uses the error's `retry_after_seconds` hint (precise wakeup — the bucket roll time is known exactly); for locks, semaphores, and ordered-lock waits it's `lock_snooze_base_delay + jitter` (a short re-poll, since a held lock or a live blocker nonce typically clears in milliseconds). Snoozes do not count against the backend's retry budget. After `lock_snooze_max_attempts` snoozes the context is marked failed (ordered-lock waits bypass the cap — see the ordered-lock docs).
|
|
661
688
|
|
|
662
689
|
On dedup hits (period gate already marked), the reactor returns a `RubyReactor::Skipped` result instead — no steps run, no exception:
|
|
663
690
|
|
|
@@ -1176,7 +1203,7 @@ Learn about the fundamental building blocks of RubyReactor: Reactors, Steps, Con
|
|
|
1176
1203
|
Deep dive into how RubyReactor manages dependencies. This guide explains how the Directed Acyclic Graph is constructed to ensure steps execute in the correct topological order, enabling automatic parallelization of independent steps.
|
|
1177
1204
|
|
|
1178
1205
|
### [Async Reactors](documentation/async_reactors.md)
|
|
1179
|
-
Explore the two asynchronous execution models: Full Reactor Async and Step-Level Async. Learn how RubyReactor leverages Sidekiq for background processing, non-blocking execution, and scalable worker management.
|
|
1206
|
+
Explore the two asynchronous execution models: Full Reactor Async and Step-Level Async. Learn how RubyReactor leverages Sidekiq or ActiveJob for background processing, non-blocking execution, and scalable worker management.
|
|
1180
1207
|
|
|
1181
1208
|
### [Composition](documentation/composition.md)
|
|
1182
1209
|
Discover how to build complex, modular workflows by composing reactors within other reactors. This guide covers inline composition, class-based composition, and how to manage dependencies between composed workflows.
|
|
@@ -1219,9 +1246,9 @@ Hook into the execution lifecycle with observer middlewares. Covers the full set
|
|
|
1219
1246
|
- [ ] Multiple storage adapters
|
|
1220
1247
|
- [X] Redis
|
|
1221
1248
|
- [ ] ActiveRecord
|
|
1222
|
-
- [
|
|
1249
|
+
- [X] Multiple Async adapters
|
|
1223
1250
|
- [X] Sidekiq
|
|
1224
|
-
- [
|
|
1251
|
+
- [X] ActiveJob
|
|
1225
1252
|
- [X] OpenTelemetry support
|
|
1226
1253
|
- [X] locks
|
|
1227
1254
|
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyReactor
|
|
4
|
+
module Adapters
|
|
5
|
+
module ActiveJob
|
|
6
|
+
# `Sidekiq::Worker` gives every job class `.perform_async` / `.perform_in`
|
|
7
|
+
# for free; ActiveJob only has `.perform_later`. Extending this onto an
|
|
8
|
+
# ActiveJob class normalizes its enqueue API to the same two class
|
|
9
|
+
# methods, so `RubyReactor::Worker` and `RubyReactor::SweeperJob` can keep
|
|
10
|
+
# calling `self.class.perform_in(...)` unchanged regardless of backend.
|
|
11
|
+
# Both methods return the job id (a String), matching what Sidekiq's
|
|
12
|
+
# native `perform_async`/`perform_in` return.
|
|
13
|
+
module Compat
|
|
14
|
+
def perform_async(*args)
|
|
15
|
+
perform_later(*args).job_id
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def perform_in(delay, *args)
|
|
19
|
+
set(wait: delay).perform_later(*args).job_id
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|