@gobing-ai/spur 0.2.9 → 0.2.11
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/package.json +1 -1
- package/spur-cli/config/rules/structure/protected-files.yaml +1 -0
- package/spur-cli/config/tasks/section-matrix.yaml +43 -53
- package/spur-cli/config/templates/task/brainstorm.md +30 -0
- package/spur-cli/config/templates/task/feature-impl.md +20 -9
- package/spur-cli/config/templates/task/issue.md +30 -0
- package/spur-cli/config/templates/task/meta.md +29 -1
- package/spur-cli/config/templates/task/review.md +27 -1
- package/spur-cli/config/templates/task/standard.md +20 -9
- package/spur-cli/config/workflows/basic.yaml +1 -1
- package/spur-cli/config/workflows/feature-dev.yaml +0 -1
- package/spur-cli/config/workflows/feature-lifecycle.yaml +0 -1
- package/spur-cli/config/workflows/planning-pipeline.yaml +0 -1
- package/spur-cli/config/workflows/task-lifecycle.yaml +0 -1
- package/spur-cli/config/workflows/task-pipeline.yaml +3 -5
- package/spur.js +173 -51
- package/spur-cli/config/tasks/templates/brainstorm.md +0 -29
- package/spur-cli/config/tasks/templates/feature-impl.md +0 -50
- package/spur-cli/config/tasks/templates/issue.md +0 -33
- package/spur-cli/config/tasks/templates/meta.md +0 -31
- package/spur-cli/config/tasks/templates/review.md +0 -53
- package/spur-cli/config/tasks/templates/standard.md +0 -48
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gobing-ai/spur",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.11",
|
|
4
4
|
"description": "Spur CLI — local-first harness for mainstream coding agents: constraint checking, workflow orchestration, agent health, and history analytics. Bun-native; exposes the `spur` command.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"spur",
|
|
@@ -14,22 +14,19 @@ variants:
|
|
|
14
14
|
standard:
|
|
15
15
|
backlog:
|
|
16
16
|
required: [Background]
|
|
17
|
-
|
|
17
|
+
optional: [Requirements, Acceptance Criteria, Q&A, Design, Plan, Solution, Testing, Review]
|
|
18
18
|
# todo = "ready to execute" (§2.3): fully specified and HITL-reviewable.
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
# the implementation change-map (file:line), authored at wip/testing.
|
|
19
|
+
# Templates intentionally carry the full lifecycle scaffold; required lists
|
|
20
|
+
# express readiness, while optional lists tolerate empty future-stage placeholders.
|
|
22
21
|
todo:
|
|
23
22
|
required: [Background, Acceptance Criteria, Design, Plan]
|
|
24
|
-
optional: [Q&A, Requirements]
|
|
25
|
-
forbidden: [Solution, Review, Testing]
|
|
23
|
+
optional: [Q&A, Requirements, Solution, Testing, Review]
|
|
26
24
|
wip:
|
|
27
25
|
required: [Background, Acceptance Criteria, Design, Plan]
|
|
28
|
-
optional: [Q&A, Requirements, Solution]
|
|
29
|
-
forbidden: [Review]
|
|
26
|
+
optional: [Q&A, Requirements, Solution, Testing, Review]
|
|
30
27
|
testing:
|
|
31
28
|
required: [Solution, Testing]
|
|
32
|
-
optional: [Background, Review]
|
|
29
|
+
optional: [Background, Requirements, Acceptance Criteria, Q&A, Design, Plan, Review]
|
|
33
30
|
# done REQUIRES the evidence trio (Solution/Testing/Review). The spec sections
|
|
34
31
|
# a task was built from (Design/Plan/AC/…) are OPTIONAL, not forbidden, so a
|
|
35
32
|
# finished task keeps its design history rather than being stripped to pass the
|
|
@@ -40,27 +37,25 @@ variants:
|
|
|
40
37
|
gate: true
|
|
41
38
|
blocked:
|
|
42
39
|
required: [Background]
|
|
43
|
-
optional: [Plan, Solution]
|
|
40
|
+
optional: [Requirements, Acceptance Criteria, Q&A, Design, Plan, Solution, Testing, Review]
|
|
44
41
|
cancelled:
|
|
45
42
|
required: []
|
|
46
|
-
optional: [Background]
|
|
43
|
+
optional: [Background, Requirements, Acceptance Criteria, Q&A, Design, Plan, Solution, Testing, Review]
|
|
47
44
|
|
|
48
45
|
# Feature implementation — AC pre-seeded from the linked feature's scenarios.
|
|
49
46
|
feature-impl:
|
|
50
47
|
backlog:
|
|
51
48
|
required: [Background]
|
|
52
|
-
|
|
49
|
+
optional: [Requirements, Acceptance Criteria, Q&A, Design, Plan, Solution, Testing, Review]
|
|
53
50
|
todo:
|
|
54
51
|
required: [Background, Acceptance Criteria, Design, Plan]
|
|
55
|
-
optional: [Q&A, Requirements]
|
|
56
|
-
forbidden: [Solution, Review, Testing]
|
|
52
|
+
optional: [Q&A, Requirements, Solution, Testing, Review]
|
|
57
53
|
wip:
|
|
58
54
|
required: [Background, Acceptance Criteria, Design, Plan]
|
|
59
|
-
optional: [Q&A, Requirements, Solution]
|
|
60
|
-
forbidden: [Review]
|
|
55
|
+
optional: [Q&A, Requirements, Solution, Testing, Review]
|
|
61
56
|
testing:
|
|
62
57
|
required: [Solution, Testing]
|
|
63
|
-
optional: [Background, Requirements, Acceptance Criteria, Design, Plan, Review]
|
|
58
|
+
optional: [Background, Requirements, Acceptance Criteria, Q&A, Design, Plan, Review]
|
|
64
59
|
# done keeps the spec sections optional (see `standard` rationale above) so a
|
|
65
60
|
# finished feature-impl task retains its Design/Plan/AC history.
|
|
66
61
|
done:
|
|
@@ -69,37 +64,35 @@ variants:
|
|
|
69
64
|
gate: true
|
|
70
65
|
blocked:
|
|
71
66
|
required: [Background]
|
|
72
|
-
optional: [Plan, Solution]
|
|
67
|
+
optional: [Requirements, Acceptance Criteria, Q&A, Design, Plan, Solution, Testing, Review]
|
|
73
68
|
cancelled:
|
|
74
69
|
required: []
|
|
75
|
-
optional: [Background]
|
|
70
|
+
optional: [Background, Requirements, Acceptance Criteria, Q&A, Design, Plan, Solution, Testing, Review]
|
|
76
71
|
|
|
77
72
|
# Bug / issue report — repro in Background, verified Root Cause, then fix.
|
|
78
73
|
issue:
|
|
79
74
|
backlog:
|
|
80
75
|
required: [Background]
|
|
81
|
-
|
|
76
|
+
optional: [Requirements, Acceptance Criteria, Q&A, Design, Plan, Root Cause, Solution, Testing, Review]
|
|
82
77
|
todo:
|
|
83
78
|
required: [Background, Root Cause, Plan]
|
|
84
|
-
optional: [Acceptance Criteria, Design, Q&A]
|
|
85
|
-
forbidden: [Solution, Review]
|
|
79
|
+
optional: [Requirements, Acceptance Criteria, Design, Q&A, Solution, Testing, Review]
|
|
86
80
|
wip:
|
|
87
81
|
required: [Background, Root Cause, Plan]
|
|
88
|
-
optional: [Design, Solution, Q&A]
|
|
89
|
-
forbidden: [Review]
|
|
82
|
+
optional: [Requirements, Acceptance Criteria, Design, Solution, Testing, Review, Q&A]
|
|
90
83
|
testing:
|
|
91
84
|
required: [Root Cause, Solution, Testing]
|
|
92
|
-
optional: [Background, Review]
|
|
85
|
+
optional: [Background, Requirements, Acceptance Criteria, Q&A, Design, Plan, Review]
|
|
93
86
|
done:
|
|
94
87
|
required: [Root Cause, Solution, Testing, Review]
|
|
95
|
-
optional: [Background, Acceptance Criteria, Design, Plan
|
|
88
|
+
optional: [Background, Requirements, Acceptance Criteria, Q&A, Design, Plan]
|
|
96
89
|
gate: true
|
|
97
90
|
blocked:
|
|
98
91
|
required: [Background]
|
|
99
|
-
optional: [Root Cause,
|
|
92
|
+
optional: [Requirements, Acceptance Criteria, Q&A, Design, Plan, Root Cause, Solution, Testing, Review]
|
|
100
93
|
cancelled:
|
|
101
94
|
required: []
|
|
102
|
-
optional: [Background]
|
|
95
|
+
optional: [Background, Requirements, Acceptance Criteria, Q&A, Design, Plan, Root Cause, Solution, Testing, Review]
|
|
103
96
|
|
|
104
97
|
# Code-review result task — the deliverable is the P1–P4 findings table.
|
|
105
98
|
#
|
|
@@ -117,74 +110,71 @@ variants:
|
|
|
117
110
|
review:
|
|
118
111
|
backlog:
|
|
119
112
|
required: [Background]
|
|
120
|
-
optional: [Plan, Review]
|
|
121
|
-
forbidden: [Testing]
|
|
113
|
+
optional: [Requirements, Acceptance Criteria, Q&A, Design, Plan, Solution, Testing, Review]
|
|
122
114
|
todo:
|
|
123
115
|
required: [Background, Plan]
|
|
124
|
-
optional: [Q&A, Requirements, Review]
|
|
125
|
-
forbidden: [Testing]
|
|
116
|
+
optional: [Q&A, Requirements, Acceptance Criteria, Design, Solution, Testing, Review]
|
|
126
117
|
wip:
|
|
127
118
|
required: [Background, Review]
|
|
128
|
-
optional: [
|
|
119
|
+
optional: [Requirements, Acceptance Criteria, Q&A, Design, Plan, Solution, Testing]
|
|
129
120
|
testing:
|
|
130
121
|
required: [Background, Review]
|
|
131
|
-
optional: [
|
|
122
|
+
optional: [Requirements, Acceptance Criteria, Q&A, Design, Plan, Solution, Testing]
|
|
132
123
|
done:
|
|
133
124
|
required: [Background, Review]
|
|
134
|
-
optional: [
|
|
125
|
+
optional: [Requirements, Acceptance Criteria, Q&A, Design, Plan, Solution, Testing]
|
|
135
126
|
gate: true
|
|
136
127
|
blocked:
|
|
137
128
|
required: [Background]
|
|
138
|
-
optional: [Plan,
|
|
129
|
+
optional: [Requirements, Acceptance Criteria, Q&A, Design, Plan, Solution, Testing, Review]
|
|
139
130
|
cancelled:
|
|
140
131
|
required: []
|
|
141
|
-
optional: [Background]
|
|
132
|
+
optional: [Background, Requirements, Acceptance Criteria, Q&A, Design, Plan, Solution, Testing, Review]
|
|
142
133
|
|
|
143
|
-
# Process / docs / chore —
|
|
134
|
+
# Process / docs / chore — same scaffold, lighter required gates.
|
|
144
135
|
meta:
|
|
145
136
|
backlog:
|
|
146
137
|
required: [Background]
|
|
147
|
-
|
|
138
|
+
optional: [Requirements, Acceptance Criteria, Q&A, Design, Plan, Solution, Testing, Review]
|
|
148
139
|
todo:
|
|
149
140
|
required: [Background, Plan]
|
|
150
|
-
optional: [Q&A, Design]
|
|
151
|
-
forbidden: [Review]
|
|
141
|
+
optional: [Requirements, Acceptance Criteria, Q&A, Design, Solution, Testing, Review]
|
|
152
142
|
wip:
|
|
153
143
|
required: [Background, Plan]
|
|
154
|
-
optional: [Q&A, Design, Solution]
|
|
155
|
-
forbidden: [Review]
|
|
144
|
+
optional: [Requirements, Acceptance Criteria, Q&A, Design, Solution, Testing, Review]
|
|
156
145
|
testing:
|
|
157
146
|
required: [Background, Plan]
|
|
158
|
-
optional: [Solution, Testing]
|
|
147
|
+
optional: [Requirements, Acceptance Criteria, Q&A, Design, Solution, Testing, Review]
|
|
159
148
|
done:
|
|
160
149
|
required: [Background, Plan]
|
|
161
|
-
optional: [Solution, Testing, Review]
|
|
150
|
+
optional: [Requirements, Acceptance Criteria, Q&A, Design, Solution, Testing, Review]
|
|
162
151
|
blocked:
|
|
163
152
|
required: [Background]
|
|
164
|
-
optional: [Plan]
|
|
153
|
+
optional: [Requirements, Acceptance Criteria, Q&A, Design, Plan, Solution, Testing, Review]
|
|
165
154
|
cancelled:
|
|
166
155
|
required: []
|
|
167
|
-
optional: [Background]
|
|
156
|
+
optional: [Background, Requirements, Acceptance Criteria, Q&A, Design, Plan, Solution, Testing, Review]
|
|
168
157
|
|
|
169
158
|
# Brainstorm template (minimal idea capture).
|
|
170
159
|
brainstorm:
|
|
171
160
|
backlog:
|
|
172
161
|
required: [Background]
|
|
173
|
-
|
|
162
|
+
optional: [Requirements, Acceptance Criteria, Q&A, Design, Plan, Solution, Testing, Review]
|
|
174
163
|
todo:
|
|
175
164
|
required: [Background]
|
|
176
|
-
optional: [Q&A]
|
|
165
|
+
optional: [Requirements, Acceptance Criteria, Q&A, Design, Plan, Solution, Testing, Review]
|
|
177
166
|
wip:
|
|
178
167
|
required: [Background, Plan]
|
|
179
|
-
optional: [Q&A, Design]
|
|
180
|
-
forbidden: [Review]
|
|
168
|
+
optional: [Requirements, Acceptance Criteria, Q&A, Design, Solution, Testing, Review]
|
|
181
169
|
testing:
|
|
182
170
|
required: [Background, Solution]
|
|
183
|
-
optional: [Testing]
|
|
184
|
-
forbidden: [Review]
|
|
171
|
+
optional: [Requirements, Acceptance Criteria, Q&A, Design, Plan, Testing, Review]
|
|
185
172
|
done:
|
|
186
173
|
required: [Background, Solution]
|
|
174
|
+
optional: [Requirements, Acceptance Criteria, Q&A, Design, Plan, Testing, Review]
|
|
187
175
|
blocked:
|
|
188
176
|
required: [Background]
|
|
177
|
+
optional: [Requirements, Acceptance Criteria, Q&A, Design, Plan, Solution, Testing, Review]
|
|
189
178
|
cancelled:
|
|
190
179
|
required: []
|
|
180
|
+
optional: [Background, Requirements, Acceptance Criteria, Q&A, Design, Plan, Solution, Testing, Review]
|
|
@@ -20,10 +20,40 @@ updated_at: "{{ CREATED_AT }}"
|
|
|
20
20
|
|
|
21
21
|
{{ BACKGROUND }}
|
|
22
22
|
|
|
23
|
+
### Requirements
|
|
24
|
+
|
|
25
|
+
<!-- Constraints the eventual direction must satisfy, if known. -->
|
|
26
|
+
|
|
27
|
+
### Acceptance Criteria
|
|
28
|
+
|
|
29
|
+
<!-- Decision criteria or success checks for the brainstorm output. Keep empty if not applicable. -->
|
|
30
|
+
|
|
23
31
|
### Q&A
|
|
24
32
|
|
|
33
|
+
<!-- Questions, answers, assumptions, and decision notes from the brainstorm. -->
|
|
34
|
+
|
|
35
|
+
### Design
|
|
36
|
+
|
|
37
|
+
<!-- Candidate approaches, tradeoffs, and selected direction. -->
|
|
38
|
+
|
|
25
39
|
### Plan
|
|
26
40
|
|
|
41
|
+
<!-- Follow-up steps or task/feature creation plan once the idea is ready to execute. -->
|
|
42
|
+
|
|
43
|
+
### Solution
|
|
44
|
+
|
|
45
|
+
<!-- Final synthesized recommendation or output from the brainstorm. -->
|
|
46
|
+
|
|
47
|
+
### Testing
|
|
48
|
+
|
|
49
|
+
<!-- Validation performed for claims, links, or feasibility. Use N/A when not applicable. -->
|
|
50
|
+
|
|
51
|
+
### Review
|
|
52
|
+
|
|
53
|
+
<!-- Risks, open concerns, and follow-up review notes. -->
|
|
54
|
+
|
|
27
55
|
### References
|
|
28
56
|
|
|
57
|
+
<!-- Links to docs, examples, related tasks/features, or external references. -->
|
|
58
|
+
|
|
29
59
|
### History
|
|
@@ -20,31 +20,42 @@ updated_at: "{{ CREATED_AT }}"
|
|
|
20
20
|
|
|
21
21
|
{{ BACKGROUND }}
|
|
22
22
|
|
|
23
|
+
### Requirements
|
|
24
|
+
|
|
25
|
+
<!-- R-numbered list derived from the linked feature or refined task scope. -->
|
|
26
|
+
|
|
23
27
|
### Acceptance Criteria
|
|
24
28
|
|
|
25
|
-
|
|
26
|
-
Feature: {{ NAME }}
|
|
29
|
+
<!-- Copy or derive real scenarios from the linked feature. Do not leave placeholder AC here. -->
|
|
27
30
|
|
|
28
|
-
|
|
29
|
-
Given a precondition
|
|
30
|
-
When an action is taken
|
|
31
|
-
Then an expected result occurs
|
|
32
|
-
```
|
|
31
|
+
### Q&A
|
|
33
32
|
|
|
34
|
-
|
|
33
|
+
<!-- Clarifications and decisions made during refinement. Keep empty if none. -->
|
|
35
34
|
|
|
36
35
|
### Design
|
|
37
36
|
|
|
37
|
+
<!-- Chosen implementation approach, key tradeoffs, invariants, and impacted surfaces. -->
|
|
38
|
+
|
|
38
39
|
### Plan
|
|
39
40
|
|
|
40
|
-
|
|
41
|
+
<!-- Ordered implementation checklist. Fill before moving to todo/wip. -->
|
|
41
42
|
|
|
42
43
|
### Solution
|
|
43
44
|
|
|
45
|
+
<!-- Filled during implementation: file:line change map and concise rationale. -->
|
|
46
|
+
|
|
44
47
|
### Testing
|
|
45
48
|
|
|
49
|
+
<!-- Filled during verification: commands run, outcomes, coverage claim or N/A. -->
|
|
50
|
+
|
|
46
51
|
### Review
|
|
47
52
|
|
|
53
|
+
<!-- Filled during review: P1-P4 findings, residual risk, and final disposition. -->
|
|
54
|
+
|
|
48
55
|
### References
|
|
49
56
|
|
|
57
|
+
{{ FEATURE_ID }}
|
|
58
|
+
|
|
59
|
+
<!-- Links to the parent feature, design docs, related tasks, or external references. -->
|
|
60
|
+
|
|
50
61
|
### History
|
|
@@ -20,14 +20,44 @@ updated_at: "{{ CREATED_AT }}"
|
|
|
20
20
|
|
|
21
21
|
{{ BACKGROUND }}
|
|
22
22
|
|
|
23
|
+
### Requirements
|
|
24
|
+
|
|
25
|
+
<!-- R-numbered expectations for the fix. Include repro/expected behavior if it helps traceability. -->
|
|
26
|
+
|
|
27
|
+
### Acceptance Criteria
|
|
28
|
+
|
|
29
|
+
<!-- Given/When/Then regression scenario or checklist proving the bug is fixed. -->
|
|
30
|
+
|
|
31
|
+
### Q&A
|
|
32
|
+
|
|
33
|
+
<!-- Clarifications and triage decisions. Keep empty if none. -->
|
|
34
|
+
|
|
35
|
+
### Design
|
|
36
|
+
|
|
37
|
+
<!-- Fix approach and tradeoffs. Keep this short unless the issue changes architecture. -->
|
|
38
|
+
|
|
39
|
+
### Plan
|
|
40
|
+
|
|
41
|
+
<!-- Ordered debugging/fix checklist. Fill before moving to todo/wip. -->
|
|
42
|
+
|
|
23
43
|
### Root Cause
|
|
24
44
|
|
|
45
|
+
<!-- Verified underlying cause with file:line evidence. Fill once reproduced/isolated. -->
|
|
46
|
+
|
|
25
47
|
### Solution
|
|
26
48
|
|
|
49
|
+
<!-- Filled during implementation: file:line change map and concise rationale. -->
|
|
50
|
+
|
|
27
51
|
### Testing
|
|
28
52
|
|
|
53
|
+
<!-- Filled during verification: regression command(s), outcomes, coverage claim or N/A. -->
|
|
54
|
+
|
|
29
55
|
### Review
|
|
30
56
|
|
|
57
|
+
<!-- Filled during review: P1-P4 findings, residual risk, and final disposition. -->
|
|
58
|
+
|
|
31
59
|
### References
|
|
32
60
|
|
|
61
|
+
<!-- Links to failing logs, related issues, tasks, docs, or external references. -->
|
|
62
|
+
|
|
33
63
|
### History
|
|
@@ -20,12 +20,40 @@ updated_at: "{{ CREATED_AT }}"
|
|
|
20
20
|
|
|
21
21
|
{{ BACKGROUND }}
|
|
22
22
|
|
|
23
|
+
### Requirements
|
|
24
|
+
|
|
25
|
+
<!-- R-numbered expectations for the process/docs/chore outcome. Keep empty if not applicable. -->
|
|
26
|
+
|
|
27
|
+
### Acceptance Criteria
|
|
28
|
+
|
|
29
|
+
<!-- Lightweight checklist or Given/When/Then if there is an observable completion condition. -->
|
|
30
|
+
|
|
31
|
+
### Q&A
|
|
32
|
+
|
|
33
|
+
<!-- Clarifications and decisions made during refinement. Keep empty if none. -->
|
|
34
|
+
|
|
35
|
+
### Design
|
|
36
|
+
|
|
37
|
+
<!-- Approach and tradeoffs for process/docs/config changes. Keep this short. -->
|
|
38
|
+
|
|
23
39
|
### Plan
|
|
24
40
|
|
|
25
|
-
|
|
41
|
+
<!-- Ordered checklist. Fill before moving to todo/wip. -->
|
|
42
|
+
|
|
43
|
+
### Solution
|
|
44
|
+
|
|
45
|
+
<!-- Filled during implementation: changed files/sections and concise rationale. -->
|
|
26
46
|
|
|
27
47
|
### Testing
|
|
28
48
|
|
|
49
|
+
<!-- Filled during verification: commands/checks run, outcomes, coverage claim or N/A. -->
|
|
50
|
+
|
|
51
|
+
### Review
|
|
52
|
+
|
|
53
|
+
<!-- Filled during review: P1-P4 findings, residual risk, and final disposition. -->
|
|
54
|
+
|
|
29
55
|
### References
|
|
30
56
|
|
|
57
|
+
<!-- Links to docs, tasks, decisions, or external references. -->
|
|
58
|
+
|
|
31
59
|
### History
|
|
@@ -4,7 +4,6 @@ name: "{{ NAME }}"
|
|
|
4
4
|
description: ""
|
|
5
5
|
status: backlog
|
|
6
6
|
type: review
|
|
7
|
-
template: review
|
|
8
7
|
profile: standard
|
|
9
8
|
feature_id: null
|
|
10
9
|
parent_wbs: null
|
|
@@ -31,12 +30,37 @@ in the reviewed PR/commit/diff). Fix in priority order (P1 → P2 → …); re-r
|
|
|
31
30
|
| P1 | | | |
|
|
32
31
|
| P2 | | | |
|
|
33
32
|
|
|
33
|
+
### Requirements
|
|
34
|
+
|
|
35
|
+
<!-- R-numbered fix requirements derived from the findings. Fill after triage/refinement. -->
|
|
36
|
+
|
|
37
|
+
### Acceptance Criteria
|
|
38
|
+
|
|
39
|
+
<!-- Checks that prove the findings were addressed. Keep empty until the review task becomes executable work. -->
|
|
40
|
+
|
|
41
|
+
### Q&A
|
|
42
|
+
|
|
43
|
+
<!-- Clarifications, false positives, accepted risk, and triage decisions. -->
|
|
44
|
+
|
|
45
|
+
### Design
|
|
46
|
+
|
|
47
|
+
<!-- Fix approach and tradeoffs if the findings require design judgment. -->
|
|
48
|
+
|
|
34
49
|
### Plan
|
|
35
50
|
|
|
36
51
|
- [ ] Fix P1 findings
|
|
37
52
|
- [ ] Fix P2 findings
|
|
53
|
+
- [ ] Fix all the remaining findings if any
|
|
38
54
|
- [ ] Re-review the changed code
|
|
39
55
|
|
|
56
|
+
### Solution
|
|
57
|
+
|
|
58
|
+
<!-- Filled during implementation: file:line change map and concise rationale. -->
|
|
59
|
+
|
|
60
|
+
### Testing
|
|
61
|
+
|
|
62
|
+
<!-- Filled during verification: commands/checks run, outcomes, coverage claim or N/A. -->
|
|
63
|
+
|
|
40
64
|
### Review
|
|
41
65
|
|
|
42
66
|
Post-implementation reflection — filled **after** the first fix round: what went wrong, what
|
|
@@ -49,4 +73,6 @@ remains to fix before closing, and any **back-issues** (new findings surfaced by
|
|
|
49
73
|
|
|
50
74
|
### References
|
|
51
75
|
|
|
76
|
+
<!-- Links to source review, dogfood report, PR/diff, related tasks, or external references. -->
|
|
77
|
+
|
|
52
78
|
### History
|
|
@@ -20,29 +20,40 @@ updated_at: "{{ CREATED_AT }}"
|
|
|
20
20
|
|
|
21
21
|
{{ BACKGROUND }}
|
|
22
22
|
|
|
23
|
+
### Requirements
|
|
24
|
+
|
|
25
|
+
<!-- R-numbered list of what must be true when this task is complete. Keep empty until requirements are known. -->
|
|
26
|
+
|
|
23
27
|
### Acceptance Criteria
|
|
24
28
|
|
|
25
|
-
|
|
26
|
-
|
|
29
|
+
<!-- Given/When/Then scenarios or a checklist derived from Requirements. Keep empty if this task has no objective AC yet. -->
|
|
30
|
+
|
|
31
|
+
### Q&A
|
|
32
|
+
|
|
33
|
+
<!-- Clarifications and decisions made during refinement. Keep empty if none. -->
|
|
27
34
|
|
|
28
|
-
|
|
29
|
-
Given a precondition
|
|
30
|
-
When an action is taken
|
|
31
|
-
Then an expected result occurs
|
|
32
|
-
```
|
|
35
|
+
### Design
|
|
33
36
|
|
|
34
|
-
|
|
37
|
+
<!-- Chosen approach, key tradeoffs, invariants, and impacted surfaces. Keep snippets short. -->
|
|
35
38
|
|
|
36
39
|
### Plan
|
|
37
40
|
|
|
38
|
-
|
|
41
|
+
<!-- Ordered implementation checklist. Fill before moving to todo/wip. -->
|
|
39
42
|
|
|
40
43
|
### Solution
|
|
41
44
|
|
|
45
|
+
<!-- Filled during implementation: file:line change map and concise rationale. -->
|
|
46
|
+
|
|
42
47
|
### Testing
|
|
43
48
|
|
|
49
|
+
<!-- Filled during verification: commands run, outcomes, coverage claim or N/A. -->
|
|
50
|
+
|
|
44
51
|
### Review
|
|
45
52
|
|
|
53
|
+
<!-- Filled during review: P1-P4 findings, residual risk, and final disposition. -->
|
|
54
|
+
|
|
46
55
|
### References
|
|
47
56
|
|
|
57
|
+
<!-- Links to features, docs, ADRs, related tasks, or external references. -->
|
|
58
|
+
|
|
48
59
|
### History
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
$schema: "@gobing-ai/spur/schemas/state-machine-workflow.schema.json"
|
|
2
1
|
# Canonical basic workflow: implement -> check -> fix until the check passes or the
|
|
3
2
|
# iteration bound is exhausted. Authored for the @gobing-ai/ts-dual-workflow-engine
|
|
4
3
|
# state-machine schema (initialState / states[].id / onEnter / top-level transitions).
|
|
5
4
|
# See Architecture Section 5 and Design Section 3.3.
|
|
5
|
+
"$schema": "@gobing-ai/spur/schemas/state-machine-workflow.schema.json"
|
|
6
6
|
name: basic
|
|
7
7
|
kind: state-machine
|
|
8
8
|
description: The canonical implement-check-fix-until-pass loop
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
$schema: "@gobing-ai/spur/schemas/state-machine-workflow.schema.json"
|
|
2
1
|
# Task execution pipeline — one run per execution attempt of a task (design §6).
|
|
3
2
|
#
|
|
4
3
|
# Orchestration is configuration (ADR-022 / §3.2): this is YAML over the existing
|
|
@@ -98,10 +97,9 @@ states:
|
|
|
98
97
|
- id: approve
|
|
99
98
|
description: >
|
|
100
99
|
Human-in-the-loop approval gate. Under `profile=auto` this state is never entered
|
|
101
|
-
(review routes around it straight to verify).
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
full `spur workflow validate` stays green. The workspace schema already supports it.
|
|
100
|
+
(review routes around it straight to verify). In interactive mode this state pauses
|
|
101
|
+
the run for `spur workflow continue` (E3), making approval an explicit operator action.
|
|
102
|
+
pause: true
|
|
105
103
|
onEnter:
|
|
106
104
|
- kind: hitl.confirm
|
|
107
105
|
options:
|
package/spur.js
CHANGED
|
@@ -32493,7 +32493,40 @@ var init_template_renderer = () => {};
|
|
|
32493
32493
|
// ../../packages/config/src/loader.ts
|
|
32494
32494
|
import { existsSync as existsSync3 } from "fs";
|
|
32495
32495
|
import { homedir } from "os";
|
|
32496
|
-
import { join as join2 } from "path";
|
|
32496
|
+
import { dirname as dirname3, join as join2 } from "path";
|
|
32497
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
32498
|
+
function embeddedSchemasId(embeddedSchemas) {
|
|
32499
|
+
if (embeddedSchemas === undefined)
|
|
32500
|
+
return "disk";
|
|
32501
|
+
let id = embeddedSchemasIds.get(embeddedSchemas);
|
|
32502
|
+
if (id === undefined) {
|
|
32503
|
+
id = nextEmbeddedSchemasId;
|
|
32504
|
+
nextEmbeddedSchemasId += 1;
|
|
32505
|
+
embeddedSchemasIds.set(embeddedSchemas, id);
|
|
32506
|
+
}
|
|
32507
|
+
return `embedded:${id}`;
|
|
32508
|
+
}
|
|
32509
|
+
function cacheKey(configPath, opts, validateJsonSchema2) {
|
|
32510
|
+
return [
|
|
32511
|
+
configPath,
|
|
32512
|
+
validateJsonSchema2 ? "schema" : "zod",
|
|
32513
|
+
opts?.schemaManifestSpecifier ?? "@gobing-ai/spur/package.json",
|
|
32514
|
+
embeddedSchemasId(opts?.embeddedSchemas)
|
|
32515
|
+
].join("\x00");
|
|
32516
|
+
}
|
|
32517
|
+
function resolveSchemaSpecifier(specifier, manifestSpecifier) {
|
|
32518
|
+
if (specifier === manifestSpecifier) {
|
|
32519
|
+
const workspaceManifest = join2(LOADER_DIR, "..", "..", "..", "apps", "cli", "package.json");
|
|
32520
|
+
if (existsSync3(workspaceManifest))
|
|
32521
|
+
return workspaceManifest;
|
|
32522
|
+
}
|
|
32523
|
+
try {
|
|
32524
|
+
const resolved = import.meta.resolve(specifier);
|
|
32525
|
+
return resolved.startsWith("file:") ? fileURLToPath3(resolved) : resolved;
|
|
32526
|
+
} catch {
|
|
32527
|
+
return specifier;
|
|
32528
|
+
}
|
|
32529
|
+
}
|
|
32497
32530
|
function resolveConfigFile(cwd) {
|
|
32498
32531
|
const projectConfig = join2(cwd ?? process.cwd(), SPUR_CONFIG_DIR, SPUR_CONFIG_FILE);
|
|
32499
32532
|
if (existsSync3(projectConfig))
|
|
@@ -32518,11 +32551,21 @@ function makeEmbeddedReader(embeddedSchemas) {
|
|
|
32518
32551
|
};
|
|
32519
32552
|
}
|
|
32520
32553
|
async function loadSpurConfig(cwd = process.cwd(), opts) {
|
|
32521
|
-
const configPath =
|
|
32522
|
-
if (
|
|
32554
|
+
const configPath = resolveConfigFile(cwd);
|
|
32555
|
+
if (configPath === undefined) {
|
|
32523
32556
|
return spurConfigSchema.parse({});
|
|
32524
32557
|
}
|
|
32525
32558
|
const validateJsonSchema2 = opts?.validateJsonSchema ?? true;
|
|
32559
|
+
const key = cacheKey(configPath, opts, validateJsonSchema2);
|
|
32560
|
+
const cached2 = spurConfigCache.get(key);
|
|
32561
|
+
if (cached2 !== undefined)
|
|
32562
|
+
return cached2;
|
|
32563
|
+
const promise2 = loadSpurConfigFile(configPath, opts, validateJsonSchema2);
|
|
32564
|
+
spurConfigCache.set(key, promise2);
|
|
32565
|
+
promise2.catch(() => spurConfigCache.delete(key));
|
|
32566
|
+
return promise2;
|
|
32567
|
+
}
|
|
32568
|
+
async function loadSpurConfigFile(configPath, opts, validateJsonSchema2) {
|
|
32526
32569
|
let raw;
|
|
32527
32570
|
if (validateJsonSchema2) {
|
|
32528
32571
|
const embeddedSchemas = opts?.embeddedSchemas;
|
|
@@ -32531,7 +32574,7 @@ async function loadSpurConfig(cwd = process.cwd(), opts) {
|
|
|
32531
32574
|
if (embeddedSchemas !== undefined && specifier === manifestSpecifier) {
|
|
32532
32575
|
return `${EMBEDDED_PREFIX}/package.json`;
|
|
32533
32576
|
}
|
|
32534
|
-
return specifier;
|
|
32577
|
+
return resolveSchemaSpecifier(specifier, manifestSpecifier);
|
|
32535
32578
|
};
|
|
32536
32579
|
const fileSystem = embeddedSchemas ? { readFile: makeEmbeddedReader(embeddedSchemas) } : undefined;
|
|
32537
32580
|
raw = await loadStructuredConfig(configPath, {
|
|
@@ -32561,7 +32604,7 @@ async function loadStructuredSpurConfig(configPath, opts) {
|
|
|
32561
32604
|
if (embeddedSchemas !== undefined && specifier === manifestSpecifier) {
|
|
32562
32605
|
return `${EMBEDDED_PREFIX}/package.json`;
|
|
32563
32606
|
}
|
|
32564
|
-
return specifier;
|
|
32607
|
+
return resolveSchemaSpecifier(specifier, manifestSpecifier);
|
|
32565
32608
|
};
|
|
32566
32609
|
const fileSystem = embeddedSchemas ? { readFile: makeEmbeddedReader(embeddedSchemas) } : undefined;
|
|
32567
32610
|
return await loadStructuredConfig(configPath, {
|
|
@@ -32584,6 +32627,14 @@ function defaultPlanningFolders() {
|
|
|
32584
32627
|
};
|
|
32585
32628
|
}
|
|
32586
32629
|
async function resolvePlanningFolders(fs) {
|
|
32630
|
+
const cached2 = planningFoldersCache.get(fs);
|
|
32631
|
+
if (cached2 !== undefined)
|
|
32632
|
+
return cached2;
|
|
32633
|
+
const promise2 = resolvePlanningFoldersUncached(fs);
|
|
32634
|
+
planningFoldersCache.set(fs, promise2);
|
|
32635
|
+
return promise2;
|
|
32636
|
+
}
|
|
32637
|
+
async function resolvePlanningFoldersUncached(fs) {
|
|
32587
32638
|
const configPath = fs.resolve(join2(SPUR_CONFIG_DIR, SPUR_CONFIG_FILE));
|
|
32588
32639
|
if (!await fs.exists(configPath))
|
|
32589
32640
|
return defaultPlanningFolders();
|
|
@@ -32614,7 +32665,7 @@ async function resolvePlanningFolders(fs) {
|
|
|
32614
32665
|
return defaultPlanningFolders();
|
|
32615
32666
|
}
|
|
32616
32667
|
}
|
|
32617
|
-
var SPUR_CONFIG_DIR = ".spur", SPUR_CONFIG_FILE = "config.yaml", GLOBAL_CONFIG_FILE, EMBEDDED_PREFIX = "\x00embedded-spur";
|
|
32668
|
+
var SPUR_CONFIG_DIR = ".spur", SPUR_CONFIG_FILE = "config.yaml", GLOBAL_CONFIG_FILE, LOADER_DIR, nextEmbeddedSchemasId = 1, embeddedSchemasIds, spurConfigCache, planningFoldersCache, EMBEDDED_PREFIX = "\x00embedded-spur";
|
|
32618
32669
|
var init_loader = __esm(() => {
|
|
32619
32670
|
init_dist3();
|
|
32620
32671
|
init_dist2();
|
|
@@ -32622,6 +32673,10 @@ var init_loader = __esm(() => {
|
|
|
32622
32673
|
init_bundled_config();
|
|
32623
32674
|
init_template_renderer();
|
|
32624
32675
|
GLOBAL_CONFIG_FILE = join2(homedir(), ".config", "spur", "config.yaml");
|
|
32676
|
+
LOADER_DIR = dirname3(fileURLToPath3(import.meta.url));
|
|
32677
|
+
embeddedSchemasIds = new WeakMap;
|
|
32678
|
+
spurConfigCache = new Map;
|
|
32679
|
+
planningFoldersCache = new WeakMap;
|
|
32625
32680
|
});
|
|
32626
32681
|
|
|
32627
32682
|
// ../../node_modules/.bun/@gobing-ai+ts-runtime@0.4.2/node_modules/@gobing-ai/ts-runtime/dist/bun-sqlite.js
|
|
@@ -55614,7 +55669,7 @@ var init_db2 = __esm(() => {
|
|
|
55614
55669
|
|
|
55615
55670
|
// ../../packages/domain/src/planning/locks.ts
|
|
55616
55671
|
import { closeSync, fsyncSync, openSync, renameSync as renameSync2 } from "fs";
|
|
55617
|
-
import { dirname as
|
|
55672
|
+
import { dirname as dirname6, join as join5 } from "path";
|
|
55618
55673
|
function lockPathForEntity(folder, entityId) {
|
|
55619
55674
|
return join5(folder, `.${entityId}.lock`);
|
|
55620
55675
|
}
|
|
@@ -55701,7 +55756,7 @@ function fsyncPath(path8, swallowErrors = false) {
|
|
|
55701
55756
|
}
|
|
55702
55757
|
}
|
|
55703
55758
|
function atomicWrite(dest, content, entityId, fs3, projectName = "spur") {
|
|
55704
|
-
const dir =
|
|
55759
|
+
const dir = dirname6(dest);
|
|
55705
55760
|
const rand = Math.random().toString(36).slice(2, 10);
|
|
55706
55761
|
const tempPath = join5(dir, `.${projectName}.${entityId}.${rand}.tmp`);
|
|
55707
55762
|
fs3.writeFile(tempPath, content);
|
|
@@ -55710,7 +55765,7 @@ function atomicWrite(dest, content, entityId, fs3, projectName = "spur") {
|
|
|
55710
55765
|
fsyncPath(dir, true);
|
|
55711
55766
|
}
|
|
55712
55767
|
async function atomicWriteAsync(dest, content, entityId, fs3, projectName = "spur") {
|
|
55713
|
-
const dir =
|
|
55768
|
+
const dir = dirname6(dest);
|
|
55714
55769
|
const rand = Math.random().toString(36).slice(2, 10);
|
|
55715
55770
|
const tempPath = join5(dir, `.${projectName}.${entityId}.${rand}.tmp`);
|
|
55716
55771
|
await fs3.writeFile(tempPath, content);
|
|
@@ -55774,6 +55829,19 @@ function findHeadings(body, hashes) {
|
|
|
55774
55829
|
}
|
|
55775
55830
|
return hits;
|
|
55776
55831
|
}
|
|
55832
|
+
function normalizeYamlScalars(data) {
|
|
55833
|
+
const out = {};
|
|
55834
|
+
for (const [key, value] of Object.entries(data)) {
|
|
55835
|
+
if (value instanceof Date) {
|
|
55836
|
+
out[key] = value.toISOString();
|
|
55837
|
+
} else if (value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
55838
|
+
out[key] = normalizeYamlScalars(value);
|
|
55839
|
+
} else {
|
|
55840
|
+
out[key] = value;
|
|
55841
|
+
}
|
|
55842
|
+
}
|
|
55843
|
+
return out;
|
|
55844
|
+
}
|
|
55777
55845
|
|
|
55778
55846
|
class MarkdownDocument {
|
|
55779
55847
|
_domain;
|
|
@@ -55804,7 +55872,7 @@ class MarkdownDocument {
|
|
|
55804
55872
|
try {
|
|
55805
55873
|
const parsed = $parse(raw);
|
|
55806
55874
|
if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
55807
|
-
data = parsed;
|
|
55875
|
+
data = normalizeYamlScalars(parsed);
|
|
55808
55876
|
}
|
|
55809
55877
|
} catch {}
|
|
55810
55878
|
frontmatter = { raw, data };
|
|
@@ -55971,8 +56039,19 @@ ${body.trimEnd()}
|
|
|
55971
56039
|
${content}
|
|
55972
56040
|
${END_MARKER}${after}`;
|
|
55973
56041
|
}
|
|
56042
|
+
yamlSafeValue(value) {
|
|
56043
|
+
if (value.startsWith('"') && value.endsWith('"'))
|
|
56044
|
+
return value;
|
|
56045
|
+
if (/[:{}[\],&*?|>!%@`#"'\\\n\r]/.test(value))
|
|
56046
|
+
return `"${value}"`;
|
|
56047
|
+
if (/^(null|true|false|yes|no|on|off)$/i.test(value))
|
|
56048
|
+
return `"${value}"`;
|
|
56049
|
+
if (/^\d/.test(value) && !/^\d{4}-\d{2}-\d{2}/.test(value))
|
|
56050
|
+
return `"${value}"`;
|
|
56051
|
+
return value;
|
|
56052
|
+
}
|
|
55974
56053
|
setFrontmatterField(key, value) {
|
|
55975
|
-
const fieldLine = `${key}: ${value}`;
|
|
56054
|
+
const fieldLine = `${key}: ${this.yamlSafeValue(value)}`;
|
|
55976
56055
|
if (this._frontmatter === null) {
|
|
55977
56056
|
const raw2 = fieldLine;
|
|
55978
56057
|
this._frontmatter = { raw: raw2, data: { [key]: value } };
|
|
@@ -58476,7 +58555,7 @@ var init_rule_service = __esm(() => {
|
|
|
58476
58555
|
});
|
|
58477
58556
|
|
|
58478
58557
|
// ../../packages/app/src/services/task-check.ts
|
|
58479
|
-
import { dirname as
|
|
58558
|
+
import { dirname as dirname7, join as join8 } from "path";
|
|
58480
58559
|
function isPlaceholderBody(body) {
|
|
58481
58560
|
const stripped = body.replace(/<!--[\s\S]*?-->/g, "").replace(/^\s*>\s*TBD\s*$/gim, "").trim();
|
|
58482
58561
|
return stripped.length === 0;
|
|
@@ -58526,6 +58605,29 @@ function isProseOnlyReview(body) {
|
|
|
58526
58605
|
return !stripped.split(`
|
|
58527
58606
|
`).some((l) => l.includes("|"));
|
|
58528
58607
|
}
|
|
58608
|
+
function hasAdjacentFileLineColumns(body) {
|
|
58609
|
+
const lines = body.split(`
|
|
58610
|
+
`);
|
|
58611
|
+
for (const line of lines) {
|
|
58612
|
+
if (!line.includes("|"))
|
|
58613
|
+
continue;
|
|
58614
|
+
const cells = line.split("|").map((c3) => c3.trim());
|
|
58615
|
+
if (cells.length < 3)
|
|
58616
|
+
continue;
|
|
58617
|
+
for (let i2 = 0;i2 < cells.length - 1; i2++) {
|
|
58618
|
+
const a2 = cells[i2];
|
|
58619
|
+
const b = cells[i2 + 1];
|
|
58620
|
+
if (a2 === undefined || b === undefined)
|
|
58621
|
+
continue;
|
|
58622
|
+
const fileText = a2.replace(/`/g, "");
|
|
58623
|
+
const fileCell = /\.(tsx?|jsx?|mjs|cjs|md|ya?ml|json|css|html|sql|sh|toml)$/i.test(fileText);
|
|
58624
|
+
const lineCell = /^\d+(-\d+)?$/.test(b);
|
|
58625
|
+
if (fileCell && lineCell)
|
|
58626
|
+
return true;
|
|
58627
|
+
}
|
|
58628
|
+
}
|
|
58629
|
+
return false;
|
|
58630
|
+
}
|
|
58529
58631
|
var TaskCheckService;
|
|
58530
58632
|
var init_task_check = __esm(() => {
|
|
58531
58633
|
init_src2();
|
|
@@ -58554,8 +58656,8 @@ var init_task_check = __esm(() => {
|
|
|
58554
58656
|
const entry = this.resolveMatrixEntry(variant, status);
|
|
58555
58657
|
this.runL2(doc2, entry, findings);
|
|
58556
58658
|
this.runL3(doc2, entry, findings);
|
|
58557
|
-
const tasksDir =
|
|
58558
|
-
const featuresDir = join8(
|
|
58659
|
+
const tasksDir = dirname7(filePath);
|
|
58660
|
+
const featuresDir = join8(dirname7(tasksDir), "features");
|
|
58559
58661
|
await this.runL4(doc2, fm, findings, featuresDir, tasksDir);
|
|
58560
58662
|
await this.runL4Rollup(doc2, wbs, status, findings, tasksDir);
|
|
58561
58663
|
return { wbs, ...this.summarizeWithStatus(status, findings, strict) };
|
|
@@ -58586,7 +58688,8 @@ var init_task_check = __esm(() => {
|
|
|
58586
58688
|
const solBody = doc2.getSection("Solution");
|
|
58587
58689
|
if (solBody !== null && !isPlaceholderBody(solBody)) {
|
|
58588
58690
|
const hasFileLine = /`[^`]+?:\d+(-\d+)?`/.test(solBody) || /[^\s`]\.\w+:\d+/.test(solBody);
|
|
58589
|
-
|
|
58691
|
+
const hasTableFileLine = hasFileLine || hasAdjacentFileLineColumns(solBody);
|
|
58692
|
+
if (!hasTableFileLine) {
|
|
58590
58693
|
findings.push({
|
|
58591
58694
|
layer: "L3",
|
|
58592
58695
|
severity: "error",
|
|
@@ -58899,7 +59002,7 @@ function renderTesting(v) {
|
|
|
58899
59002
|
lines.push(`| ${req.id} | ${req.status} | ${evidence} |`);
|
|
58900
59003
|
}
|
|
58901
59004
|
}
|
|
58902
|
-
lines.push("");
|
|
59005
|
+
lines.push("- Coverage: N/A (verdict-based; verify pipeline does not measure code coverage)");
|
|
58903
59006
|
return lines.join(`
|
|
58904
59007
|
`);
|
|
58905
59008
|
}
|
|
@@ -58997,7 +59100,7 @@ function gitDiffU0(cwd) {
|
|
|
58997
59100
|
var init_task_record = () => {};
|
|
58998
59101
|
|
|
58999
59102
|
// ../../packages/app/src/services/task-service.ts
|
|
59000
|
-
import { dirname as
|
|
59103
|
+
import { dirname as dirname8, isAbsolute, relative as relative2, resolve as resolve2 } from "path";
|
|
59001
59104
|
function patchFrontmatterField(rendered, key, value) {
|
|
59002
59105
|
const existingRe = new RegExp(`^(?=\\s*${escapeRegex2(key)}:)`, "m");
|
|
59003
59106
|
if (existingRe.test(rendered)) {
|
|
@@ -59054,7 +59157,7 @@ function sectionIsBare(doc2, name) {
|
|
|
59054
59157
|
const body = doc2.getSection(name);
|
|
59055
59158
|
if (body === null)
|
|
59056
59159
|
return true;
|
|
59057
|
-
const trimmed = body.trim();
|
|
59160
|
+
const trimmed = body.replace(/<!--[\s\S]*?-->/g, "").trim();
|
|
59058
59161
|
if (trimmed === "")
|
|
59059
59162
|
return true;
|
|
59060
59163
|
if (/^Pipeline run \d{4}\b/.test(trimmed))
|
|
@@ -59590,7 +59693,7 @@ ${block}` : block);
|
|
|
59590
59693
|
resolveListDir(folder) {
|
|
59591
59694
|
if (folder === undefined)
|
|
59592
59695
|
return this.ctx.tasksDir;
|
|
59593
|
-
const root = resolve2(
|
|
59696
|
+
const root = resolve2(dirname8(this.ctx.tasksDir));
|
|
59594
59697
|
const candidate = resolve2(root, folder);
|
|
59595
59698
|
const rel = relative2(root, candidate);
|
|
59596
59699
|
if (rel.startsWith("..") || isAbsolute(rel)) {
|
|
@@ -59864,7 +59967,7 @@ var init_team_service = __esm(() => {
|
|
|
59864
59967
|
|
|
59865
59968
|
// ../../packages/app/src/workflow/actions/agent-run.ts
|
|
59866
59969
|
import { mkdir, writeFile } from "fs/promises";
|
|
59867
|
-
import { dirname as
|
|
59970
|
+
import { dirname as dirname9, isAbsolute as isAbsolute2, join as join10 } from "path";
|
|
59868
59971
|
|
|
59869
59972
|
class AgentRunActionRunner {
|
|
59870
59973
|
kind = KIND;
|
|
@@ -59916,7 +60019,7 @@ class AgentRunActionRunner {
|
|
|
59916
60019
|
const ok2 = exitCode2 === 0;
|
|
59917
60020
|
if (answerFile !== undefined) {
|
|
59918
60021
|
const target = isAbsolute2(answerFile) ? answerFile : join10(cwd, answerFile);
|
|
59919
|
-
await mkdir(
|
|
60022
|
+
await mkdir(dirname9(target), { recursive: true });
|
|
59920
60023
|
await writeFile(target, answer, "utf8");
|
|
59921
60024
|
}
|
|
59922
60025
|
return {
|
|
@@ -61232,7 +61335,7 @@ init_loader();
|
|
|
61232
61335
|
init_dist4();
|
|
61233
61336
|
init_dist3();
|
|
61234
61337
|
import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync4 } from "fs";
|
|
61235
|
-
import { dirname as
|
|
61338
|
+
import { dirname as dirname4 } from "path";
|
|
61236
61339
|
|
|
61237
61340
|
// ../../node_modules/.bun/@gobing-ai+ts-infra@0.4.2+be3e9d72efacb189/node_modules/@gobing-ai/ts-infra/dist/application/index.js
|
|
61238
61341
|
init_default_observers();
|
|
@@ -64634,7 +64737,7 @@ function validateAppConfig(validator, raw, section, filePath) {
|
|
|
64634
64737
|
function createFileSink(filePath) {
|
|
64635
64738
|
return (line) => {
|
|
64636
64739
|
try {
|
|
64637
|
-
mkdirSync2(
|
|
64740
|
+
mkdirSync2(dirname4(filePath), { recursive: true });
|
|
64638
64741
|
appendFileSync3(filePath, line);
|
|
64639
64742
|
} catch {}
|
|
64640
64743
|
};
|
|
@@ -66052,8 +66155,8 @@ var figlet = (() => {
|
|
|
66052
66155
|
})();
|
|
66053
66156
|
|
|
66054
66157
|
// ../../node_modules/.bun/figlet@1.11.0/node_modules/figlet/dist/node-figlet.mjs
|
|
66055
|
-
import { fileURLToPath as
|
|
66056
|
-
var __filename2 =
|
|
66158
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
66159
|
+
var __filename2 = fileURLToPath4(import.meta.url);
|
|
66057
66160
|
var __dirname2 = path7.dirname(__filename2);
|
|
66058
66161
|
var fontPath = path7.join(__dirname2, "/../fonts/");
|
|
66059
66162
|
var nodeFiglet = figlet;
|
|
@@ -68864,7 +68967,7 @@ import { join as join13, resolve as resolve4 } from "path";
|
|
|
68864
68967
|
var CLI_CONFIG = {
|
|
68865
68968
|
binaryName: "spur",
|
|
68866
68969
|
binaryLabel: "spur",
|
|
68867
|
-
binaryVersion: "0.2.
|
|
68970
|
+
binaryVersion: "0.2.11",
|
|
68868
68971
|
configDir: ".spur",
|
|
68869
68972
|
configFile: ".spur/config.yaml",
|
|
68870
68973
|
databaseFile: ".spur/spur.db"
|
|
@@ -68874,21 +68977,21 @@ var CLI_CONFIG = {
|
|
|
68874
68977
|
var SCAFFOLD_MANIFEST = [
|
|
68875
68978
|
{ source: "rules/recommended-pre-check.yaml", target: "rules/recommended-pre-check.yaml" },
|
|
68876
68979
|
{ source: "rules/recommended-post-check.yaml", target: "rules/recommended-post-check.yaml" },
|
|
68877
|
-
{ source: "workflows/basic.yaml", target: "
|
|
68878
|
-
{ source: "workflows/task-lifecycle.yaml", target: "
|
|
68879
|
-
{ source: "workflows/feature-lifecycle.yaml", target: "
|
|
68880
|
-
{ source: "workflows/feature-dev.yaml", target: "
|
|
68881
|
-
{ source: "workflows/task-pipeline.yaml", target: "
|
|
68882
|
-
{ source: "workflows/planning-pipeline.yaml", target: "
|
|
68980
|
+
{ source: "workflows/basic.yaml", target: "workflows/basic.yaml" },
|
|
68981
|
+
{ source: "workflows/task-lifecycle.yaml", target: "workflows/task-lifecycle.yaml" },
|
|
68982
|
+
{ source: "workflows/feature-lifecycle.yaml", target: "workflows/feature-lifecycle.yaml" },
|
|
68983
|
+
{ source: "workflows/feature-dev.yaml", target: "workflows/feature-dev.yaml" },
|
|
68984
|
+
{ source: "workflows/task-pipeline.yaml", target: "workflows/task-pipeline.yaml" },
|
|
68985
|
+
{ source: "workflows/planning-pipeline.yaml", target: "workflows/planning-pipeline.yaml" },
|
|
68883
68986
|
{ source: "tasks/section-matrix.yaml", target: "tasks/section-matrix.yaml" },
|
|
68884
68987
|
{ source: "templates/task/standard.md", target: "tasks/templates/standard.md" },
|
|
68885
68988
|
{ source: "templates/task/feature-impl.md", target: "tasks/templates/feature-impl.md" },
|
|
68886
68989
|
{ source: "templates/task/issue.md", target: "tasks/templates/issue.md" },
|
|
68887
68990
|
{ source: "templates/task/review.md", target: "tasks/templates/review.md" },
|
|
68888
68991
|
{ source: "templates/task/meta.md", target: "tasks/templates/meta.md" },
|
|
68889
|
-
{ source: "templates/feature/default.md", target: "
|
|
68890
|
-
{ source: "templates/bdd/gherkin.md", target: "
|
|
68891
|
-
{ source: "templates/bdd/checklist.md", target: "
|
|
68992
|
+
{ source: "templates/feature/default.md", target: "templates/feature/default.md" },
|
|
68993
|
+
{ source: "templates/bdd/gherkin.md", target: "templates/bdd/gherkin.md" },
|
|
68994
|
+
{ source: "templates/bdd/checklist.md", target: "templates/bdd/checklist.md" },
|
|
68892
68995
|
{
|
|
68893
68996
|
source: "templates/docs/99_PROJECT_CONSTITUTION.md",
|
|
68894
68997
|
target: "docs/99_PROJECT_CONSTITUTION.md",
|
|
@@ -68901,13 +69004,13 @@ var SCAFFOLD_MANIFEST = [
|
|
|
68901
69004
|
{ source: "templates/docs/03_ARCHITECTURE.md", target: "docs/03_ARCHITECTURE.md", root: true, preserve: true },
|
|
68902
69005
|
{ source: "templates/docs/04_DESIGN.md", target: "docs/04_DESIGN.md", root: true, preserve: true },
|
|
68903
69006
|
{ source: "templates/docs/05_FEATURES.md", target: "docs/05_FEATURES.md", root: true, preserve: true },
|
|
68904
|
-
{ source: "templates/docs/99_PROJECT_CONSTITUTION.md", target: "
|
|
68905
|
-
{ source: "templates/docs/00_ADR.md", target: "
|
|
68906
|
-
{ source: "templates/docs/01_PRD.md", target: "
|
|
68907
|
-
{ source: "templates/docs/02_ROADMAP.md", target: "
|
|
68908
|
-
{ source: "templates/docs/03_ARCHITECTURE.md", target: "
|
|
68909
|
-
{ source: "templates/docs/04_DESIGN.md", target: "
|
|
68910
|
-
{ source: "templates/docs/05_FEATURES.md", target: "
|
|
69007
|
+
{ source: "templates/docs/99_PROJECT_CONSTITUTION.md", target: "templates/docs/99_PROJECT_CONSTITUTION.md" },
|
|
69008
|
+
{ source: "templates/docs/00_ADR.md", target: "templates/docs/00_ADR.md" },
|
|
69009
|
+
{ source: "templates/docs/01_PRD.md", target: "templates/docs/01_PRD.md" },
|
|
69010
|
+
{ source: "templates/docs/02_ROADMAP.md", target: "templates/docs/02_ROADMAP.md" },
|
|
69011
|
+
{ source: "templates/docs/03_ARCHITECTURE.md", target: "templates/docs/03_ARCHITECTURE.md" },
|
|
69012
|
+
{ source: "templates/docs/04_DESIGN.md", target: "templates/docs/04_DESIGN.md" },
|
|
69013
|
+
{ source: "templates/docs/05_FEATURES.md", target: "templates/docs/05_FEATURES.md" }
|
|
68911
69014
|
];
|
|
68912
69015
|
|
|
68913
69016
|
// src/commands/init.ts
|
|
@@ -69394,7 +69497,7 @@ init_src();
|
|
|
69394
69497
|
|
|
69395
69498
|
// ../server/src/serve.ts
|
|
69396
69499
|
init_src3();
|
|
69397
|
-
import { dirname as
|
|
69500
|
+
import { dirname as dirname10, isAbsolute as isAbsolute3, join as join16 } from "path";
|
|
69398
69501
|
init_dist3();
|
|
69399
69502
|
|
|
69400
69503
|
// ../server/src/bootstrap.ts
|
|
@@ -72635,15 +72738,15 @@ var getPattern = (label, next) => {
|
|
|
72635
72738
|
}
|
|
72636
72739
|
const match = label.match(/^\:([^\{\}]+)(?:\{(.+)\})?$/);
|
|
72637
72740
|
if (match) {
|
|
72638
|
-
const
|
|
72639
|
-
if (!patternCache[
|
|
72741
|
+
const cacheKey2 = `${label}#${next}`;
|
|
72742
|
+
if (!patternCache[cacheKey2]) {
|
|
72640
72743
|
if (match[2]) {
|
|
72641
|
-
patternCache[
|
|
72744
|
+
patternCache[cacheKey2] = next && next[0] !== ":" && next[0] !== "*" ? [cacheKey2, match[1], new RegExp(`^${match[2]}(?=/${next})`)] : [label, match[1], new RegExp(`^${match[2]}$`)];
|
|
72642
72745
|
} else {
|
|
72643
|
-
patternCache[
|
|
72746
|
+
patternCache[cacheKey2] = [label, match[1], true];
|
|
72644
72747
|
}
|
|
72645
72748
|
}
|
|
72646
|
-
return patternCache[
|
|
72749
|
+
return patternCache[cacheKey2];
|
|
72647
72750
|
}
|
|
72648
72751
|
return null;
|
|
72649
72752
|
};
|
|
@@ -77367,7 +77470,7 @@ var defaultDeps = {
|
|
|
77367
77470
|
async function resolveWebDistPath(configuredPath) {
|
|
77368
77471
|
const candidates = configuredPath && configuredPath.trim() !== "" ? [isAbsolute3(configuredPath) ? configuredPath : join16(process.cwd(), configuredPath)] : [
|
|
77369
77472
|
join16(process.cwd(), "dist/web"),
|
|
77370
|
-
join16(
|
|
77473
|
+
join16(dirname10(process.execPath), "../web"),
|
|
77371
77474
|
join16(import.meta.dir, "../../../dist/web")
|
|
77372
77475
|
];
|
|
77373
77476
|
for (const candidate of candidates) {
|
|
@@ -78135,6 +78238,15 @@ ${result.content}`);
|
|
|
78135
78238
|
} else {
|
|
78136
78239
|
context3.output.write(`${result.ref.id}: ${result.fromStatus} \u2192 ${result.toStatus}`);
|
|
78137
78240
|
}
|
|
78241
|
+
if (status === "done" && !options.json) {
|
|
78242
|
+
const task2 = await svc.show(wbs);
|
|
78243
|
+
const featureId2 = task2.frontmatter.feature_id;
|
|
78244
|
+
if (!featureId2 || featureId2.length === 0) {
|
|
78245
|
+
context3.output.write(`\u24D8 Task ${wbs} is now done but has no feature_id.
|
|
78246
|
+
` + ` Link it with: spur task update ${wbs} --feature <id>
|
|
78247
|
+
` + " (Advisory only \u2014 the task is already done.)");
|
|
78248
|
+
}
|
|
78249
|
+
}
|
|
78138
78250
|
} else {
|
|
78139
78251
|
context3.output.error("Either <status>, --section/--from-file, or --feature/--priority is required");
|
|
78140
78252
|
context3.setExitCode(2);
|
|
@@ -78436,7 +78548,17 @@ async function runDoneGateCheck(context3, wbs, folderOverride) {
|
|
|
78436
78548
|
const result = await svc.check(`${tasksDir}/${fileName}`, wbs, { strict: false });
|
|
78437
78549
|
return result.pass;
|
|
78438
78550
|
}
|
|
78551
|
+
var sectionMatrixCache = new Map;
|
|
78439
78552
|
async function loadSectionMatrix(projectRoot) {
|
|
78553
|
+
const cached2 = sectionMatrixCache.get(projectRoot);
|
|
78554
|
+
if (cached2 !== undefined)
|
|
78555
|
+
return cached2;
|
|
78556
|
+
const promise2 = loadSectionMatrixUncached(projectRoot);
|
|
78557
|
+
sectionMatrixCache.set(projectRoot, promise2);
|
|
78558
|
+
promise2.catch(() => sectionMatrixCache.delete(projectRoot));
|
|
78559
|
+
return promise2;
|
|
78560
|
+
}
|
|
78561
|
+
async function loadSectionMatrixUncached(projectRoot) {
|
|
78440
78562
|
const localPath = join18(projectRoot, ".spur", "tasks", "section-matrix.yaml");
|
|
78441
78563
|
if (existsSync5(localPath)) {
|
|
78442
78564
|
const data = await loadStructuredSpurConfig(localPath, {
|
|
@@ -78826,7 +78948,7 @@ init_src3();
|
|
|
78826
78948
|
init_src();
|
|
78827
78949
|
init_src2();
|
|
78828
78950
|
init_dist3();
|
|
78829
|
-
import { dirname as
|
|
78951
|
+
import { dirname as dirname11, join as join19, resolve as resolve6 } from "path";
|
|
78830
78952
|
import { isatty as isatty3 } from "tty";
|
|
78831
78953
|
|
|
78832
78954
|
// ../../node_modules/.bun/@clack+core@1.4.1/node_modules/@clack/core/dist/index.mjs
|
|
@@ -80238,7 +80360,7 @@ async function createMigratedDbAdapter(cwd = process.cwd(), env = process.env, d
|
|
|
80238
80360
|
const configuredUrl = env.DATABASE_URL === undefined ? join19(cwd, CLI_CONFIG.databaseFile) : config3.database.url;
|
|
80239
80361
|
const url2 = dbUrl ?? configuredUrl;
|
|
80240
80362
|
if (url2 !== ":memory:") {
|
|
80241
|
-
await createNodeFileSystem().ensureDir(
|
|
80363
|
+
await createNodeFileSystem().ensureDir(dirname11(url2));
|
|
80242
80364
|
}
|
|
80243
80365
|
return createMigratedDb({ url: url2 });
|
|
80244
80366
|
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
schema_version: 1
|
|
3
|
-
name: "{{ NAME }}"
|
|
4
|
-
description: ""
|
|
5
|
-
status: backlog
|
|
6
|
-
type: brainstorm
|
|
7
|
-
profile: standard
|
|
8
|
-
feature_id: null
|
|
9
|
-
parent_wbs: null
|
|
10
|
-
priority: P2
|
|
11
|
-
tags: []
|
|
12
|
-
dependencies: []
|
|
13
|
-
created_at: "{{ CREATED_AT }}"
|
|
14
|
-
updated_at: "{{ CREATED_AT }}"
|
|
15
|
-
---
|
|
16
|
-
|
|
17
|
-
## {{ WBS }}. {{ NAME }}
|
|
18
|
-
|
|
19
|
-
### Background
|
|
20
|
-
|
|
21
|
-
{{ BACKGROUND }}
|
|
22
|
-
|
|
23
|
-
### Q&A
|
|
24
|
-
|
|
25
|
-
### Plan
|
|
26
|
-
|
|
27
|
-
### References
|
|
28
|
-
|
|
29
|
-
### History
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
schema_version: 1
|
|
3
|
-
name: "{{ NAME }}"
|
|
4
|
-
description: ""
|
|
5
|
-
status: backlog
|
|
6
|
-
type: task
|
|
7
|
-
profile: standard
|
|
8
|
-
feature_id: null
|
|
9
|
-
parent_wbs: null
|
|
10
|
-
priority: P2
|
|
11
|
-
tags: []
|
|
12
|
-
dependencies: []
|
|
13
|
-
created_at: "{{ CREATED_AT }}"
|
|
14
|
-
updated_at: "{{ CREATED_AT }}"
|
|
15
|
-
---
|
|
16
|
-
|
|
17
|
-
## {{ WBS }}. {{ NAME }}
|
|
18
|
-
|
|
19
|
-
### Background
|
|
20
|
-
|
|
21
|
-
{{ BACKGROUND }}
|
|
22
|
-
|
|
23
|
-
### Acceptance Criteria
|
|
24
|
-
|
|
25
|
-
```gherkin
|
|
26
|
-
Feature: {{ NAME }}
|
|
27
|
-
|
|
28
|
-
Scenario: Basic acceptance
|
|
29
|
-
Given a precondition
|
|
30
|
-
When an action is taken
|
|
31
|
-
Then an expected result occurs
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
- [ ] Acceptance checklist item
|
|
35
|
-
|
|
36
|
-
### Design
|
|
37
|
-
|
|
38
|
-
### Plan
|
|
39
|
-
|
|
40
|
-
- [ ] Implementation step
|
|
41
|
-
|
|
42
|
-
### Solution
|
|
43
|
-
|
|
44
|
-
### Testing
|
|
45
|
-
|
|
46
|
-
### Review
|
|
47
|
-
|
|
48
|
-
### References
|
|
49
|
-
|
|
50
|
-
### History
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
schema_version: 1
|
|
3
|
-
name: "{{ NAME }}"
|
|
4
|
-
description: ""
|
|
5
|
-
status: backlog
|
|
6
|
-
type: issue
|
|
7
|
-
profile: standard
|
|
8
|
-
feature_id: null
|
|
9
|
-
parent_wbs: null
|
|
10
|
-
priority: P2
|
|
11
|
-
tags: ["bug"]
|
|
12
|
-
dependencies: []
|
|
13
|
-
created_at: "{{ CREATED_AT }}"
|
|
14
|
-
updated_at: "{{ CREATED_AT }}"
|
|
15
|
-
---
|
|
16
|
-
|
|
17
|
-
## {{ WBS }}. {{ NAME }}
|
|
18
|
-
|
|
19
|
-
### Background
|
|
20
|
-
|
|
21
|
-
{{ BACKGROUND }}
|
|
22
|
-
|
|
23
|
-
### Root Cause
|
|
24
|
-
|
|
25
|
-
### Solution
|
|
26
|
-
|
|
27
|
-
### Testing
|
|
28
|
-
|
|
29
|
-
### Review
|
|
30
|
-
|
|
31
|
-
### References
|
|
32
|
-
|
|
33
|
-
### History
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
schema_version: 1
|
|
3
|
-
name: "{{ NAME }}"
|
|
4
|
-
description: ""
|
|
5
|
-
status: backlog
|
|
6
|
-
type: meta
|
|
7
|
-
profile: standard
|
|
8
|
-
feature_id: null
|
|
9
|
-
parent_wbs: null
|
|
10
|
-
priority: P2
|
|
11
|
-
tags: ["meta"]
|
|
12
|
-
dependencies: []
|
|
13
|
-
created_at: "{{ CREATED_AT }}"
|
|
14
|
-
updated_at: "{{ CREATED_AT }}"
|
|
15
|
-
---
|
|
16
|
-
|
|
17
|
-
## {{ WBS }}. {{ NAME }}
|
|
18
|
-
|
|
19
|
-
### Background
|
|
20
|
-
|
|
21
|
-
{{ BACKGROUND }}
|
|
22
|
-
|
|
23
|
-
### Plan
|
|
24
|
-
|
|
25
|
-
- [ ] Implementation step
|
|
26
|
-
|
|
27
|
-
### Testing
|
|
28
|
-
|
|
29
|
-
### References
|
|
30
|
-
|
|
31
|
-
### History
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
schema_version: 1
|
|
3
|
-
name: "{{ NAME }}"
|
|
4
|
-
description: ""
|
|
5
|
-
status: backlog
|
|
6
|
-
type: review
|
|
7
|
-
template: review
|
|
8
|
-
profile: standard
|
|
9
|
-
feature_id: null
|
|
10
|
-
parent_wbs: null
|
|
11
|
-
priority: P2
|
|
12
|
-
tags: ["review"]
|
|
13
|
-
dependencies: []
|
|
14
|
-
created_at: "{{ CREATED_AT }}"
|
|
15
|
-
updated_at: "{{ CREATED_AT }}"
|
|
16
|
-
---
|
|
17
|
-
|
|
18
|
-
## {{ WBS }}. {{ NAME }}
|
|
19
|
-
|
|
20
|
-
### Background
|
|
21
|
-
|
|
22
|
-
{{ BACKGROUND }}
|
|
23
|
-
|
|
24
|
-
#### Review Findings
|
|
25
|
-
|
|
26
|
-
The code-review findings this task must address — logged here as **input** (what was found
|
|
27
|
-
in the reviewed PR/commit/diff). Fix in priority order (P1 → P2 → …); re-review after.
|
|
28
|
-
|
|
29
|
-
| Severity | File | Finding | Recommendation |
|
|
30
|
-
| -------- | ---- | ------- | -------------- |
|
|
31
|
-
| P1 | | | |
|
|
32
|
-
| P2 | | | |
|
|
33
|
-
|
|
34
|
-
### Plan
|
|
35
|
-
|
|
36
|
-
- [ ] Fix P1 findings
|
|
37
|
-
- [ ] Fix P2 findings
|
|
38
|
-
- [ ] Fix all the remaining findings if any
|
|
39
|
-
- [ ] Re-review the changed code
|
|
40
|
-
|
|
41
|
-
### Review
|
|
42
|
-
|
|
43
|
-
Post-implementation reflection — filled **after** the first fix round: what went wrong, what
|
|
44
|
-
remains to fix before closing, and any **back-issues** (new findings surfaced by the fix).
|
|
45
|
-
|
|
46
|
-
| Severity | File | Finding | Recommendation |
|
|
47
|
-
| -------- | ---- | ------- | -------------- |
|
|
48
|
-
| P1 | | | |
|
|
49
|
-
| P2 | | | |
|
|
50
|
-
|
|
51
|
-
### References
|
|
52
|
-
|
|
53
|
-
### History
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
schema_version: 1
|
|
3
|
-
name: "{{ NAME }}"
|
|
4
|
-
description: ""
|
|
5
|
-
status: backlog
|
|
6
|
-
type: task
|
|
7
|
-
profile: standard
|
|
8
|
-
feature_id: null
|
|
9
|
-
parent_wbs: null
|
|
10
|
-
priority: P2
|
|
11
|
-
tags: []
|
|
12
|
-
dependencies: []
|
|
13
|
-
created_at: "{{ CREATED_AT }}"
|
|
14
|
-
updated_at: "{{ CREATED_AT }}"
|
|
15
|
-
---
|
|
16
|
-
|
|
17
|
-
## {{ WBS }}. {{ NAME }}
|
|
18
|
-
|
|
19
|
-
### Background
|
|
20
|
-
|
|
21
|
-
{{ BACKGROUND }}
|
|
22
|
-
|
|
23
|
-
### Acceptance Criteria
|
|
24
|
-
|
|
25
|
-
```gherkin
|
|
26
|
-
Feature: {{ NAME }}
|
|
27
|
-
|
|
28
|
-
Scenario: Basic acceptance
|
|
29
|
-
Given a precondition
|
|
30
|
-
When an action is taken
|
|
31
|
-
Then an expected result occurs
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
- [ ] Acceptance checklist item
|
|
35
|
-
|
|
36
|
-
### Plan
|
|
37
|
-
|
|
38
|
-
- [ ] Implementation step
|
|
39
|
-
|
|
40
|
-
### Solution
|
|
41
|
-
|
|
42
|
-
### Testing
|
|
43
|
-
|
|
44
|
-
### Review
|
|
45
|
-
|
|
46
|
-
### References
|
|
47
|
-
|
|
48
|
-
### History
|