agileflow 2.91.0 → 2.92.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 +5 -0
- package/README.md +3 -3
- package/lib/README.md +178 -0
- package/lib/codebase-indexer.js +31 -23
- package/lib/colors.js +190 -12
- package/lib/consent.js +232 -0
- package/lib/correlation.js +277 -0
- package/lib/error-codes.js +46 -0
- package/lib/errors.js +48 -6
- package/lib/file-cache.js +182 -0
- package/lib/format-error.js +156 -0
- package/lib/path-resolver.js +155 -7
- package/lib/paths.js +212 -20
- package/lib/placeholder-registry.js +205 -0
- package/lib/registry-di.js +358 -0
- package/lib/result-schema.js +363 -0
- package/lib/result.js +210 -0
- package/lib/session-registry.js +13 -0
- package/lib/session-state-machine.js +465 -0
- package/lib/validate-commands.js +308 -0
- package/lib/validate.js +116 -52
- package/package.json +1 -1
- package/scripts/af +34 -0
- package/scripts/agent-loop.js +63 -9
- package/scripts/agileflow-configure.js +2 -2
- package/scripts/agileflow-welcome.js +435 -23
- package/scripts/archive-completed-stories.sh +57 -11
- package/scripts/claude-tmux.sh +102 -0
- package/scripts/damage-control-bash.js +3 -70
- package/scripts/damage-control-edit.js +3 -20
- package/scripts/damage-control-write.js +3 -20
- package/scripts/dependency-check.js +310 -0
- package/scripts/get-env.js +11 -4
- package/scripts/lib/configure-detect.js +23 -1
- package/scripts/lib/configure-features.js +43 -2
- package/scripts/lib/context-formatter.js +771 -0
- package/scripts/lib/context-loader.js +699 -0
- package/scripts/lib/damage-control-utils.js +107 -0
- package/scripts/lib/json-utils.sh +162 -0
- package/scripts/lib/state-migrator.js +353 -0
- package/scripts/lib/story-state-machine.js +437 -0
- package/scripts/obtain-context.js +80 -1248
- package/scripts/pre-push-check.sh +46 -0
- package/scripts/precompact-context.sh +23 -10
- package/scripts/query-codebase.js +122 -14
- package/scripts/ralph-loop.js +5 -5
- package/scripts/session-manager.js +220 -42
- package/scripts/spawn-parallel.js +651 -0
- package/scripts/tui/blessed/data/watcher.js +20 -15
- package/scripts/tui/blessed/index.js +2 -2
- package/scripts/tui/blessed/panels/output.js +14 -8
- package/scripts/tui/blessed/panels/sessions.js +22 -15
- package/scripts/tui/blessed/panels/trace.js +14 -8
- package/scripts/tui/blessed/ui/help.js +3 -3
- package/scripts/tui/blessed/ui/screen.js +4 -4
- package/scripts/tui/blessed/ui/statusbar.js +5 -9
- package/scripts/tui/blessed/ui/tabbar.js +11 -11
- package/scripts/validators/component-validator.js +41 -14
- package/scripts/validators/json-schema-validator.js +11 -4
- package/scripts/validators/markdown-validator.js +1 -2
- package/scripts/validators/migration-validator.js +17 -5
- package/scripts/validators/security-validator.js +137 -33
- package/scripts/validators/story-format-validator.js +31 -10
- package/scripts/validators/test-result-validator.js +19 -4
- package/scripts/validators/workflow-validator.js +12 -5
- package/src/core/agents/codebase-query.md +24 -0
- package/src/core/commands/adr.md +114 -0
- package/src/core/commands/agent.md +120 -0
- package/src/core/commands/assign.md +145 -0
- package/src/core/commands/babysit.md +32 -5
- package/src/core/commands/changelog.md +118 -0
- package/src/core/commands/configure.md +42 -6
- package/src/core/commands/diagnose.md +114 -0
- package/src/core/commands/epic.md +113 -0
- package/src/core/commands/handoff.md +128 -0
- package/src/core/commands/help.md +75 -0
- package/src/core/commands/pr.md +96 -0
- package/src/core/commands/roadmap/analyze.md +400 -0
- package/src/core/commands/session/new.md +113 -6
- package/src/core/commands/session/spawn.md +197 -0
- package/src/core/commands/sprint.md +22 -0
- package/src/core/commands/status.md +74 -0
- package/src/core/commands/story.md +143 -4
- package/src/core/templates/agileflow-metadata.json +55 -2
- package/src/core/templates/plan-template.md +125 -0
- package/src/core/templates/story-lifecycle.md +213 -0
- package/src/core/templates/story-template.md +4 -0
- package/src/core/templates/tdd-test-template.js +241 -0
- package/tools/cli/commands/setup.js +86 -0
- package/tools/cli/installers/core/installer.js +94 -0
- package/tools/cli/installers/ide/_base-ide.js +20 -11
- package/tools/cli/installers/ide/codex.js +29 -47
- package/tools/cli/lib/config-manager.js +17 -2
- package/tools/cli/lib/content-transformer.js +271 -0
- package/tools/cli/lib/error-handler.js +14 -22
- package/tools/cli/lib/ide-error-factory.js +421 -0
- package/tools/cli/lib/ide-health-monitor.js +364 -0
- package/tools/cli/lib/ide-registry.js +114 -1
- package/tools/cli/lib/ui.js +14 -25
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
# Story Lifecycle
|
|
2
|
+
|
|
3
|
+
This document describes how stories transition through different states in AgileFlow.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## State Diagram
|
|
8
|
+
|
|
9
|
+
```mermaid
|
|
10
|
+
stateDiagram-v2
|
|
11
|
+
[*] --> draft: create
|
|
12
|
+
|
|
13
|
+
draft --> ready: validate (DoR met)
|
|
14
|
+
draft --> draft: edit
|
|
15
|
+
|
|
16
|
+
ready --> in_progress: start work
|
|
17
|
+
ready --> blocked: dependency issue
|
|
18
|
+
|
|
19
|
+
in_progress --> in_review: submit PR
|
|
20
|
+
in_progress --> blocked: blocker found
|
|
21
|
+
in_progress --> ready: pause work
|
|
22
|
+
|
|
23
|
+
blocked --> ready: unblocked
|
|
24
|
+
blocked --> in_progress: unblocked (continue)
|
|
25
|
+
|
|
26
|
+
in_review --> done: PR merged
|
|
27
|
+
in_review --> in_progress: changes requested
|
|
28
|
+
|
|
29
|
+
done --> [*]
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Story States
|
|
35
|
+
|
|
36
|
+
| State | Description | Next Actions |
|
|
37
|
+
|-------|-------------|--------------|
|
|
38
|
+
| **draft** | Story created but not ready for work | Validate, add AC, set estimate |
|
|
39
|
+
| **ready** | Definition of Ready (DoR) met | Start work, assign owner |
|
|
40
|
+
| **in-progress** | Actively being worked on | Complete, submit PR, or pause |
|
|
41
|
+
| **blocked** | Waiting on external dependency | Resolve blocker, escalate |
|
|
42
|
+
| **in-review** | Pull request submitted | Review, approve, merge |
|
|
43
|
+
| **done** | Work complete and merged | Archive, celebrate |
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## State Transitions
|
|
48
|
+
|
|
49
|
+
### draft → ready
|
|
50
|
+
|
|
51
|
+
**Trigger**: Story passes Definition of Ready (DoR)
|
|
52
|
+
|
|
53
|
+
**DoR Checklist**:
|
|
54
|
+
- [ ] Clear acceptance criteria (Given/When/Then)
|
|
55
|
+
- [ ] Estimate provided
|
|
56
|
+
- [ ] Dependencies identified
|
|
57
|
+
- [ ] Owner assigned
|
|
58
|
+
- [ ] Epic linked
|
|
59
|
+
|
|
60
|
+
**Command**: `/agileflow:story-validate STORY=US-XXXX`
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
### ready → in-progress
|
|
65
|
+
|
|
66
|
+
**Trigger**: Developer starts work
|
|
67
|
+
|
|
68
|
+
**Actions**:
|
|
69
|
+
1. Update status.json: `status: "in-progress"`
|
|
70
|
+
2. Set phase to `execute`
|
|
71
|
+
3. Log to bus/log.jsonl
|
|
72
|
+
|
|
73
|
+
**Command**: `/agileflow:status STORY=US-XXXX STATUS=in-progress`
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
### in-progress → in-review
|
|
78
|
+
|
|
79
|
+
**Trigger**: Pull request created
|
|
80
|
+
|
|
81
|
+
**Actions**:
|
|
82
|
+
1. Update status.json: `status: "in-review"`
|
|
83
|
+
2. Set phase to `audit`
|
|
84
|
+
3. Add PR link
|
|
85
|
+
4. Log to bus/log.jsonl
|
|
86
|
+
|
|
87
|
+
**Command**: `/agileflow:status STORY=US-XXXX STATUS=in-review PR=<url>`
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
### in-review → done
|
|
92
|
+
|
|
93
|
+
**Trigger**: PR merged to main
|
|
94
|
+
|
|
95
|
+
**Actions**:
|
|
96
|
+
1. Update status.json: `status: "done"`
|
|
97
|
+
2. Set phase to `complete`
|
|
98
|
+
3. Log completion to bus/log.jsonl
|
|
99
|
+
4. Auto-archive after 7 days
|
|
100
|
+
|
|
101
|
+
**Command**: `/agileflow:status STORY=US-XXXX STATUS=done`
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
### → blocked
|
|
106
|
+
|
|
107
|
+
**Trigger**: External dependency or blocker found
|
|
108
|
+
|
|
109
|
+
**Actions**:
|
|
110
|
+
1. Update status.json: `status: "blocked"`
|
|
111
|
+
2. Add blocker reason
|
|
112
|
+
3. Log to bus/log.jsonl
|
|
113
|
+
4. Notify dependent teams
|
|
114
|
+
|
|
115
|
+
**Command**: `/agileflow:status STORY=US-XXXX STATUS=blocked SUMMARY="Waiting on API keys"`
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Phase Mapping
|
|
120
|
+
|
|
121
|
+
Stories have both a **status** and a **phase**. Phases group statuses for high-level tracking:
|
|
122
|
+
|
|
123
|
+
| Status | Phase | Description |
|
|
124
|
+
|--------|-------|-------------|
|
|
125
|
+
| draft | plan | Planning and scoping |
|
|
126
|
+
| ready | plan | Ready for planning/assignment |
|
|
127
|
+
| in-progress | execute | Active development |
|
|
128
|
+
| blocked | execute | Blocked but in execute phase |
|
|
129
|
+
| in-review | audit | Quality review |
|
|
130
|
+
| done | complete | Work finished |
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## WIP Limits
|
|
135
|
+
|
|
136
|
+
AgileFlow enforces Work-In-Progress (WIP) limits to prevent overload:
|
|
137
|
+
|
|
138
|
+
- **Per Agent**: Max 2 stories in `in-progress` + `in-review`
|
|
139
|
+
- **Per Epic**: Configurable, typically 5-10 concurrent stories
|
|
140
|
+
|
|
141
|
+
**Violation handling**:
|
|
142
|
+
```
|
|
143
|
+
⚠️ WIP Limit Exceeded
|
|
144
|
+
|
|
145
|
+
AG-UI has 3 stories in progress (limit: 2):
|
|
146
|
+
- US-0042: Login Form
|
|
147
|
+
- US-0043: Profile Page
|
|
148
|
+
- US-0044: Dashboard
|
|
149
|
+
|
|
150
|
+
Complete or pause a story before starting new work.
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## Lifecycle in status.json
|
|
156
|
+
|
|
157
|
+
```json
|
|
158
|
+
{
|
|
159
|
+
"stories": {
|
|
160
|
+
"US-0042": {
|
|
161
|
+
"id": "US-0042",
|
|
162
|
+
"title": "Login Form with Validation",
|
|
163
|
+
"epic": "EP-0010",
|
|
164
|
+
"owner": "AG-UI",
|
|
165
|
+
"status": "in-progress",
|
|
166
|
+
"phase": "execute",
|
|
167
|
+
"estimate": "2h",
|
|
168
|
+
"deps": ["US-0041"],
|
|
169
|
+
"created": "2026-01-20T10:00:00Z",
|
|
170
|
+
"updated": "2026-01-21T14:30:00Z",
|
|
171
|
+
"history": [
|
|
172
|
+
{"status": "draft", "ts": "2026-01-20T10:00:00Z"},
|
|
173
|
+
{"status": "ready", "ts": "2026-01-20T11:00:00Z"},
|
|
174
|
+
{"status": "in-progress", "ts": "2026-01-21T09:00:00Z"}
|
|
175
|
+
]
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## Bus Messages
|
|
184
|
+
|
|
185
|
+
State transitions are logged to `docs/09-agents/bus/log.jsonl`:
|
|
186
|
+
|
|
187
|
+
```jsonl
|
|
188
|
+
{"ts":"2026-01-21T09:00:00Z","type":"status","from":"AG-UI","to":"ALL","story":"US-0042","status":"in-progress","text":"Started work on login form"}
|
|
189
|
+
{"ts":"2026-01-21T14:30:00Z","type":"status","from":"AG-UI","to":"ALL","story":"US-0042","status":"in-review","pr":"https://github.com/.../pull/42"}
|
|
190
|
+
{"ts":"2026-01-21T16:00:00Z","type":"status","from":"AG-UI","to":"ALL","story":"US-0042","status":"done","text":"PR merged"}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## Related Commands
|
|
196
|
+
|
|
197
|
+
| Command | Purpose |
|
|
198
|
+
|---------|---------|
|
|
199
|
+
| `/agileflow:story` | Create new story |
|
|
200
|
+
| `/agileflow:status` | Update story status |
|
|
201
|
+
| `/agileflow:board` | View kanban board |
|
|
202
|
+
| `/agileflow:blockers` | View blocked stories |
|
|
203
|
+
| `/agileflow:story-validate` | Validate DoR |
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## Best Practices
|
|
208
|
+
|
|
209
|
+
1. **Keep stories small**: 1-3 days of work maximum
|
|
210
|
+
2. **Update status promptly**: Reflect actual work state
|
|
211
|
+
3. **Document blockers**: Include reason and expected resolution
|
|
212
|
+
4. **Respect WIP limits**: Complete before starting new
|
|
213
|
+
5. **Use phase handoffs**: Capture context at transitions
|
|
@@ -8,6 +8,8 @@ estimate: {{ESTIMATE}}
|
|
|
8
8
|
created: {{CREATED}}
|
|
9
9
|
updated: {{UPDATED}}
|
|
10
10
|
dependencies: {{DEPENDENCIES}}
|
|
11
|
+
tdd_mode: {{TDD_MODE}}
|
|
12
|
+
test_file: {{TEST_FILE}}
|
|
11
13
|
---
|
|
12
14
|
|
|
13
15
|
# {{STORY_ID}}: {{TITLE}}
|
|
@@ -15,6 +17,7 @@ dependencies: {{DEPENDENCIES}}
|
|
|
15
17
|
**Epic**: {{EPIC_ID}}
|
|
16
18
|
**Owner**: {{OWNER}}
|
|
17
19
|
**Estimate**: {{ESTIMATE}}
|
|
20
|
+
{{TDD_BADGE}}
|
|
18
21
|
|
|
19
22
|
## Description
|
|
20
23
|
{{DESCRIPTION}}
|
|
@@ -50,6 +53,7 @@ dependencies: {{DEPENDENCIES}}
|
|
|
50
53
|
|
|
51
54
|
## Testing Strategy
|
|
52
55
|
See: `docs/07-testing/test-cases/{{STORY_ID}}.md`
|
|
56
|
+
{{TDD_TEST_FILE_REF}}
|
|
53
57
|
|
|
54
58
|
## Dependencies
|
|
55
59
|
{{DEPENDENCIES}}
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TDD Test Template for AgileFlow Story Creation
|
|
3
|
+
*
|
|
4
|
+
* This template generates framework-specific test stubs from acceptance criteria.
|
|
5
|
+
* Used by /agileflow:story when TDD=true flag is set.
|
|
6
|
+
*
|
|
7
|
+
* Placeholders:
|
|
8
|
+
* - {{STORY_ID}} - Story identifier (e.g., US-0042)
|
|
9
|
+
* - {{TITLE}} - Story title
|
|
10
|
+
* - {{EPIC}} - Epic identifier (e.g., EP-0010)
|
|
11
|
+
* - {{AC_TESTS}} - Generated test cases from acceptance criteria
|
|
12
|
+
* - {{CREATED}} - ISO timestamp
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
// Template for Jest/Vitest (JavaScript/TypeScript)
|
|
16
|
+
const jestTemplate = `/**
|
|
17
|
+
* TDD Tests for {{STORY_ID}}: {{TITLE}}
|
|
18
|
+
*
|
|
19
|
+
* Story: docs/06-stories/{{EPIC}}/{{STORY_ID}}-*.md
|
|
20
|
+
* Created: {{CREATED}}
|
|
21
|
+
*
|
|
22
|
+
* These tests are generated from acceptance criteria.
|
|
23
|
+
* Implementation goal: Make all tests pass.
|
|
24
|
+
*
|
|
25
|
+
* Workflow:
|
|
26
|
+
* 1. Review each test case (currently skipped)
|
|
27
|
+
* 2. Implement the feature code
|
|
28
|
+
* 3. Remove .skip from tests one at a time
|
|
29
|
+
* 4. Run tests until all pass
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
describe('{{STORY_ID}}: {{TITLE}}', () => {
|
|
33
|
+
{{AC_TESTS}}
|
|
34
|
+
});
|
|
35
|
+
`;
|
|
36
|
+
|
|
37
|
+
// Template for individual AC test block (Jest)
|
|
38
|
+
const jestAcTemplate = `
|
|
39
|
+
// {{AC_LABEL}}: {{AC_SUMMARY}}
|
|
40
|
+
describe('{{AC_SUMMARY}}', () => {
|
|
41
|
+
it.skip('should {{THEN_CLAUSE}}', () => {
|
|
42
|
+
// Given: {{GIVEN_CLAUSE}}
|
|
43
|
+
// TODO: Set up test preconditions
|
|
44
|
+
|
|
45
|
+
// When: {{WHEN_CLAUSE}}
|
|
46
|
+
// TODO: Execute the action
|
|
47
|
+
|
|
48
|
+
// Then: {{THEN_CLAUSE}}
|
|
49
|
+
// TODO: Add assertions
|
|
50
|
+
expect(true).toBe(true);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
`;
|
|
54
|
+
|
|
55
|
+
// Template for pytest (Python)
|
|
56
|
+
const pytestTemplate = `"""
|
|
57
|
+
TDD Tests for {{STORY_ID}}: {{TITLE}}
|
|
58
|
+
|
|
59
|
+
Story: docs/06-stories/{{EPIC}}/{{STORY_ID}}-*.md
|
|
60
|
+
Created: {{CREATED}}
|
|
61
|
+
|
|
62
|
+
These tests are generated from acceptance criteria.
|
|
63
|
+
Implementation goal: Make all tests pass.
|
|
64
|
+
|
|
65
|
+
Workflow:
|
|
66
|
+
1. Review each test case (currently skipped)
|
|
67
|
+
2. Implement the feature code
|
|
68
|
+
3. Remove @pytest.mark.skip from tests one at a time
|
|
69
|
+
4. Run tests until all pass
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
import pytest
|
|
73
|
+
|
|
74
|
+
{{AC_TESTS}}
|
|
75
|
+
`;
|
|
76
|
+
|
|
77
|
+
// Template for individual AC test block (pytest)
|
|
78
|
+
const pytestAcTemplate = `
|
|
79
|
+
# {{AC_LABEL}}: {{AC_SUMMARY}}
|
|
80
|
+
class Test{{AC_CLASS_NAME}}:
|
|
81
|
+
@pytest.mark.skip(reason="TDD: Implement feature first")
|
|
82
|
+
def test_{{TEST_NAME}}(self):
|
|
83
|
+
"""
|
|
84
|
+
Given: {{GIVEN_CLAUSE}}
|
|
85
|
+
When: {{WHEN_CLAUSE}}
|
|
86
|
+
Then: {{THEN_CLAUSE}}
|
|
87
|
+
"""
|
|
88
|
+
# TODO: Set up test preconditions (Given)
|
|
89
|
+
|
|
90
|
+
# TODO: Execute the action (When)
|
|
91
|
+
|
|
92
|
+
# TODO: Add assertions (Then)
|
|
93
|
+
assert True
|
|
94
|
+
`;
|
|
95
|
+
|
|
96
|
+
// Template for Go tests
|
|
97
|
+
const goTestTemplate = `package {{PACKAGE_NAME}}_test
|
|
98
|
+
|
|
99
|
+
/*
|
|
100
|
+
TDD Tests for {{STORY_ID}}: {{TITLE}}
|
|
101
|
+
|
|
102
|
+
Story: docs/06-stories/{{EPIC}}/{{STORY_ID}}-*.md
|
|
103
|
+
Created: {{CREATED}}
|
|
104
|
+
|
|
105
|
+
These tests are generated from acceptance criteria.
|
|
106
|
+
Implementation goal: Make all tests pass.
|
|
107
|
+
*/
|
|
108
|
+
|
|
109
|
+
import (
|
|
110
|
+
"testing"
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
{{AC_TESTS}}
|
|
114
|
+
`;
|
|
115
|
+
|
|
116
|
+
// Template for individual AC test block (Go)
|
|
117
|
+
const goAcTemplate = `
|
|
118
|
+
// {{AC_LABEL}}: {{AC_SUMMARY}}
|
|
119
|
+
func Test{{AC_FUNC_NAME}}(t *testing.T) {
|
|
120
|
+
t.Skip("TDD: Implement feature first")
|
|
121
|
+
|
|
122
|
+
// Given: {{GIVEN_CLAUSE}}
|
|
123
|
+
// TODO: Set up test preconditions
|
|
124
|
+
|
|
125
|
+
// When: {{WHEN_CLAUSE}}
|
|
126
|
+
// TODO: Execute the action
|
|
127
|
+
|
|
128
|
+
// Then: {{THEN_CLAUSE}}
|
|
129
|
+
// TODO: Add assertions
|
|
130
|
+
}
|
|
131
|
+
`;
|
|
132
|
+
|
|
133
|
+
// Export templates for use by story.md command
|
|
134
|
+
module.exports = {
|
|
135
|
+
jest: {
|
|
136
|
+
file: jestTemplate,
|
|
137
|
+
ac: jestAcTemplate,
|
|
138
|
+
extension: '.test.ts',
|
|
139
|
+
directory: '__tests__'
|
|
140
|
+
},
|
|
141
|
+
vitest: {
|
|
142
|
+
file: jestTemplate, // Same format as Jest
|
|
143
|
+
ac: jestAcTemplate,
|
|
144
|
+
extension: '.test.ts',
|
|
145
|
+
directory: '__tests__'
|
|
146
|
+
},
|
|
147
|
+
pytest: {
|
|
148
|
+
file: pytestTemplate,
|
|
149
|
+
ac: pytestAcTemplate,
|
|
150
|
+
extension: '_test.py',
|
|
151
|
+
directory: 'tests'
|
|
152
|
+
},
|
|
153
|
+
go: {
|
|
154
|
+
file: goTestTemplate,
|
|
155
|
+
ac: goAcTemplate,
|
|
156
|
+
extension: '_test.go',
|
|
157
|
+
directory: ''
|
|
158
|
+
},
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Parse acceptance criteria into structured format
|
|
162
|
+
* @param {string} acText - Raw AC text with Given/When/Then
|
|
163
|
+
* @returns {Array} Parsed AC objects
|
|
164
|
+
*/
|
|
165
|
+
parseAcceptanceCriteria(acText) {
|
|
166
|
+
const criteria = [];
|
|
167
|
+
const acBlocks = acText.split(/(?=(?:AC\d+|###?\s*AC))/i);
|
|
168
|
+
|
|
169
|
+
for (const block of acBlocks) {
|
|
170
|
+
if (!block.trim()) continue;
|
|
171
|
+
|
|
172
|
+
// Extract label (AC1, AC2, etc.)
|
|
173
|
+
const labelMatch = block.match(/(?:AC\s*)?(\d+)/i);
|
|
174
|
+
const label = labelMatch ? `AC${labelMatch[1]}` : `AC${criteria.length + 1}`;
|
|
175
|
+
|
|
176
|
+
// Extract Given/When/Then
|
|
177
|
+
const givenMatch = block.match(/\*?\*?Given\*?\*?[:\s]+(.+?)(?=\*?\*?When|$)/is);
|
|
178
|
+
const whenMatch = block.match(/\*?\*?When\*?\*?[:\s]+(.+?)(?=\*?\*?Then|$)/is);
|
|
179
|
+
const thenMatch = block.match(/\*?\*?Then\*?\*?[:\s]+(.+?)(?=\*?\*?(?:Given|AC|###)|$)/is);
|
|
180
|
+
|
|
181
|
+
if (givenMatch || whenMatch || thenMatch) {
|
|
182
|
+
criteria.push({
|
|
183
|
+
label,
|
|
184
|
+
given: givenMatch ? givenMatch[1].trim() : 'preconditions are met',
|
|
185
|
+
when: whenMatch ? whenMatch[1].trim() : 'action is performed',
|
|
186
|
+
then: thenMatch ? thenMatch[1].trim() : 'expected result occurs',
|
|
187
|
+
summary: this.generateSummary(givenMatch, whenMatch, thenMatch)
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// If no structured AC found, create one from the whole text
|
|
193
|
+
if (criteria.length === 0 && acText.trim()) {
|
|
194
|
+
criteria.push({
|
|
195
|
+
label: 'AC1',
|
|
196
|
+
given: 'the feature is implemented',
|
|
197
|
+
when: 'user interacts with it',
|
|
198
|
+
then: 'expected behavior occurs',
|
|
199
|
+
summary: acText.substring(0, 50).trim()
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return criteria;
|
|
204
|
+
},
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Generate a short summary from Given/When/Then
|
|
208
|
+
*/
|
|
209
|
+
generateSummary(givenMatch, whenMatch, thenMatch) {
|
|
210
|
+
if (thenMatch) {
|
|
211
|
+
// Use Then clause, truncated
|
|
212
|
+
return thenMatch[1].trim().substring(0, 50).replace(/[^\w\s]/g, '');
|
|
213
|
+
}
|
|
214
|
+
if (whenMatch) {
|
|
215
|
+
return whenMatch[1].trim().substring(0, 50).replace(/[^\w\s]/g, '');
|
|
216
|
+
}
|
|
217
|
+
return 'acceptance criteria';
|
|
218
|
+
},
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Convert string to valid function/class name
|
|
222
|
+
*/
|
|
223
|
+
toIdentifier(str) {
|
|
224
|
+
return str
|
|
225
|
+
.replace(/[^\w\s]/g, '')
|
|
226
|
+
.split(/\s+/)
|
|
227
|
+
.map((word, i) => i === 0 ? word.toLowerCase() : word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
|
228
|
+
.join('');
|
|
229
|
+
},
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Convert string to PascalCase for class names
|
|
233
|
+
*/
|
|
234
|
+
toPascalCase(str) {
|
|
235
|
+
return str
|
|
236
|
+
.replace(/[^\w\s]/g, '')
|
|
237
|
+
.split(/\s+/)
|
|
238
|
+
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
|
239
|
+
.join('');
|
|
240
|
+
}
|
|
241
|
+
};
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
const chalk = require('chalk');
|
|
9
9
|
const path = require('node:path');
|
|
10
|
+
const fs = require('node:fs');
|
|
10
11
|
const { spawnSync } = require('node:child_process');
|
|
11
12
|
const semver = require('semver');
|
|
12
13
|
const { Installer } = require('../installers/core/installer');
|
|
@@ -90,6 +91,7 @@ module.exports = {
|
|
|
90
91
|
agileflowFolder: '.agileflow',
|
|
91
92
|
docsFolder: 'docs',
|
|
92
93
|
updateGitignore: true,
|
|
94
|
+
claudeMdReinforcement: true,
|
|
93
95
|
};
|
|
94
96
|
} else {
|
|
95
97
|
// Interactive prompts
|
|
@@ -110,6 +112,16 @@ module.exports = {
|
|
|
110
112
|
success(`Installed ${coreResult.counts.commands} commands`);
|
|
111
113
|
success(`Installed ${coreResult.counts.skills} skills`);
|
|
112
114
|
|
|
115
|
+
// Report shell alias setup
|
|
116
|
+
if (coreResult.shellAliases) {
|
|
117
|
+
if (coreResult.shellAliases.configured.length > 0) {
|
|
118
|
+
success(`Added 'af' alias to: ${coreResult.shellAliases.configured.join(', ')}`);
|
|
119
|
+
}
|
|
120
|
+
if (coreResult.shellAliases.skipped.length > 0) {
|
|
121
|
+
info(`Shell aliases skipped: ${coreResult.shellAliases.skipped.join(', ')}`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
113
125
|
// Setup IDE configurations
|
|
114
126
|
displaySection('Configuring IDEs');
|
|
115
127
|
|
|
@@ -133,6 +145,73 @@ module.exports = {
|
|
|
133
145
|
}
|
|
134
146
|
}
|
|
135
147
|
|
|
148
|
+
// CLAUDE.md reinforcement for /babysit AskUserQuestion rules
|
|
149
|
+
if (config.claudeMdReinforcement) {
|
|
150
|
+
const claudeMdPath = path.join(config.directory, 'CLAUDE.md');
|
|
151
|
+
const claudeMdMarker = '<!-- AGILEFLOW_BABYSIT_RULES -->';
|
|
152
|
+
const claudeMdContent = `
|
|
153
|
+
|
|
154
|
+
${claudeMdMarker}
|
|
155
|
+
## AgileFlow /babysit Context Preservation Rules
|
|
156
|
+
|
|
157
|
+
When \`/agileflow:babysit\` is active (check session-state.json), these rules are MANDATORY:
|
|
158
|
+
|
|
159
|
+
1. **ALWAYS end responses with the AskUserQuestion tool** - Not text like "What next?" but the ACTUAL TOOL CALL
|
|
160
|
+
2. **Use Plan Mode for non-trivial tasks** - Call \`EnterPlanMode\` before complex implementations
|
|
161
|
+
3. **Delegate complex work to domain experts** - Use \`Task\` tool with appropriate \`subagent_type\`
|
|
162
|
+
4. **Track progress with TodoWrite** - For any task with 3+ steps
|
|
163
|
+
|
|
164
|
+
These rules persist across conversation compaction. Check \`docs/09-agents/session-state.json\` for active commands.
|
|
165
|
+
${claudeMdMarker}
|
|
166
|
+
`;
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
let existingContent = '';
|
|
170
|
+
if (fs.existsSync(claudeMdPath)) {
|
|
171
|
+
existingContent = fs.readFileSync(claudeMdPath, 'utf8');
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Only append if marker doesn't exist
|
|
175
|
+
if (!existingContent.includes(claudeMdMarker)) {
|
|
176
|
+
fs.appendFileSync(claudeMdPath, claudeMdContent);
|
|
177
|
+
success('Added /babysit rules to CLAUDE.md');
|
|
178
|
+
} else {
|
|
179
|
+
info('CLAUDE.md already has /babysit rules');
|
|
180
|
+
}
|
|
181
|
+
} catch (err) {
|
|
182
|
+
warning(`Could not update CLAUDE.md: ${err.message}`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Update metadata with config tracking
|
|
187
|
+
try {
|
|
188
|
+
const metadataPath = path.join(config.directory, config.docsFolder, '00-meta', 'agileflow-metadata.json');
|
|
189
|
+
if (fs.existsSync(metadataPath)) {
|
|
190
|
+
const metadata = JSON.parse(fs.readFileSync(metadataPath, 'utf8'));
|
|
191
|
+
const packageJson = require(path.join(__dirname, '..', '..', '..', 'package.json'));
|
|
192
|
+
|
|
193
|
+
// Track config schema version and profile
|
|
194
|
+
metadata.config_schema_version = packageJson.version;
|
|
195
|
+
metadata.active_profile = options.yes ? 'default' : null; // null = custom
|
|
196
|
+
|
|
197
|
+
// Track config options that were configured
|
|
198
|
+
if (!metadata.agileflow) metadata.agileflow = {};
|
|
199
|
+
if (!metadata.agileflow.config_options) metadata.agileflow.config_options = {};
|
|
200
|
+
|
|
201
|
+
metadata.agileflow.config_options.claudeMdReinforcement = {
|
|
202
|
+
available_since: '2.92.0',
|
|
203
|
+
configured: true,
|
|
204
|
+
enabled: config.claudeMdReinforcement,
|
|
205
|
+
configured_at: new Date().toISOString(),
|
|
206
|
+
description: 'Add /babysit AskUserQuestion rules to CLAUDE.md',
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
fs.writeFileSync(metadataPath, JSON.stringify(metadata, null, 2) + '\n');
|
|
210
|
+
}
|
|
211
|
+
} catch (err) {
|
|
212
|
+
// Silently fail - metadata tracking is non-critical
|
|
213
|
+
}
|
|
214
|
+
|
|
136
215
|
// Final summary
|
|
137
216
|
console.log(chalk.green('\n✨ Setup complete!\n'));
|
|
138
217
|
|
|
@@ -141,6 +220,13 @@ module.exports = {
|
|
|
141
220
|
info(`Run 'npx agileflow status' to check setup`);
|
|
142
221
|
info(`Run 'npx agileflow update' to get updates`);
|
|
143
222
|
|
|
223
|
+
// Shell alias reload reminder
|
|
224
|
+
if (coreResult.shellAliases?.configured?.length > 0) {
|
|
225
|
+
console.log(chalk.bold('\nShell aliases:'));
|
|
226
|
+
info(`Reload shell to use: ${chalk.cyan('source ~/.bashrc')} or ${chalk.cyan('source ~/.zshrc')}`);
|
|
227
|
+
info(`Then run ${chalk.cyan('af')} to start Claude in tmux (or ${chalk.cyan('claude')} for normal)`);
|
|
228
|
+
}
|
|
229
|
+
|
|
144
230
|
console.log(chalk.dim(`\nInstalled to: ${coreResult.path}\n`));
|
|
145
231
|
|
|
146
232
|
process.exit(0);
|
|
@@ -164,6 +164,10 @@ class Installer {
|
|
|
164
164
|
spinner.text = 'Installing changelog...';
|
|
165
165
|
await this.installChangelog(agileflowDir, { force: effectiveForce });
|
|
166
166
|
|
|
167
|
+
// Set up shell aliases for claude command
|
|
168
|
+
spinner.text = 'Setting up shell aliases...';
|
|
169
|
+
const aliasResult = await this.setupShellAliases(directory, { force: effectiveForce });
|
|
170
|
+
|
|
167
171
|
// Create config.yaml
|
|
168
172
|
spinner.text = 'Creating configuration...';
|
|
169
173
|
await this.createConfig(agileflowDir, userName, agileflowFolder, { force: effectiveForce });
|
|
@@ -191,6 +195,7 @@ class Installer {
|
|
|
191
195
|
projectDir: directory,
|
|
192
196
|
counts,
|
|
193
197
|
fileOps,
|
|
198
|
+
shellAliases: aliasResult,
|
|
194
199
|
};
|
|
195
200
|
} catch (error) {
|
|
196
201
|
spinner.fail('Installation failed');
|
|
@@ -813,6 +818,95 @@ class Installer {
|
|
|
813
818
|
}
|
|
814
819
|
}
|
|
815
820
|
|
|
821
|
+
/**
|
|
822
|
+
* Set up shell aliases for the claude command
|
|
823
|
+
* Adds aliases to ~/.bashrc and/or ~/.zshrc so 'claude' auto-spawns tmux
|
|
824
|
+
* @param {string} directory - Project directory (used for relative path in alias)
|
|
825
|
+
* @param {Object} options - Setup options
|
|
826
|
+
* @param {boolean} options.force - Overwrite existing aliases
|
|
827
|
+
* @returns {Promise<Object>} Result with shells configured
|
|
828
|
+
*/
|
|
829
|
+
async setupShellAliases(directory, options = {}) {
|
|
830
|
+
const os = require('os');
|
|
831
|
+
const result = {
|
|
832
|
+
configured: [],
|
|
833
|
+
skipped: [],
|
|
834
|
+
error: null,
|
|
835
|
+
};
|
|
836
|
+
|
|
837
|
+
// Only set up aliases on Unix-like systems
|
|
838
|
+
if (process.platform === 'win32') {
|
|
839
|
+
result.skipped.push('Windows (not supported)');
|
|
840
|
+
return result;
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
const homeDir = os.homedir();
|
|
844
|
+
const aliasBlock = `
|
|
845
|
+
# AgileFlow tmux wrapper
|
|
846
|
+
# Use 'af' or 'agileflow' for tmux, 'claude' stays normal
|
|
847
|
+
alias af="bash .agileflow/scripts/af"
|
|
848
|
+
alias agileflow="bash .agileflow/scripts/af"
|
|
849
|
+
`;
|
|
850
|
+
|
|
851
|
+
const marker = '# AgileFlow tmux wrapper';
|
|
852
|
+
const rcFiles = [
|
|
853
|
+
{ name: 'bash', path: path.join(homeDir, '.bashrc') },
|
|
854
|
+
{ name: 'zsh', path: path.join(homeDir, '.zshrc') },
|
|
855
|
+
];
|
|
856
|
+
|
|
857
|
+
for (const rc of rcFiles) {
|
|
858
|
+
try {
|
|
859
|
+
// Check if RC file exists
|
|
860
|
+
if (!(await fs.pathExists(rc.path))) {
|
|
861
|
+
result.skipped.push(`${rc.name} (no ${path.basename(rc.path)})`);
|
|
862
|
+
continue;
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
const content = await fs.readFile(rc.path, 'utf8');
|
|
866
|
+
|
|
867
|
+
// Check if aliases already exist
|
|
868
|
+
if (content.includes(marker)) {
|
|
869
|
+
if (options.force) {
|
|
870
|
+
// Remove existing block and re-add
|
|
871
|
+
const lines = content.split('\n');
|
|
872
|
+
const filteredLines = [];
|
|
873
|
+
let inBlock = false;
|
|
874
|
+
|
|
875
|
+
for (const line of lines) {
|
|
876
|
+
if (line.includes(marker)) {
|
|
877
|
+
inBlock = true;
|
|
878
|
+
continue;
|
|
879
|
+
}
|
|
880
|
+
if (inBlock && line.startsWith('alias ')) {
|
|
881
|
+
continue;
|
|
882
|
+
}
|
|
883
|
+
if (inBlock && line.trim() === '') {
|
|
884
|
+
inBlock = false;
|
|
885
|
+
continue;
|
|
886
|
+
}
|
|
887
|
+
inBlock = false;
|
|
888
|
+
filteredLines.push(line);
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
await fs.writeFile(rc.path, filteredLines.join('\n') + aliasBlock, 'utf8');
|
|
892
|
+
result.configured.push(rc.name);
|
|
893
|
+
} else {
|
|
894
|
+
result.skipped.push(`${rc.name} (already configured)`);
|
|
895
|
+
}
|
|
896
|
+
continue;
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
// Append aliases to RC file
|
|
900
|
+
await fs.appendFile(rc.path, aliasBlock);
|
|
901
|
+
result.configured.push(rc.name);
|
|
902
|
+
} catch (err) {
|
|
903
|
+
result.skipped.push(`${rc.name} (error: ${err.message})`);
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
return result;
|
|
908
|
+
}
|
|
909
|
+
|
|
816
910
|
/**
|
|
817
911
|
* Get installation status
|
|
818
912
|
* @param {string} directory - Project directory
|