specweave 1.0.301 → 1.0.304
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/dist/plugins/specweave-github/lib/github-ac-comment-poster.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/github-ac-comment-poster.js +44 -25
- package/dist/plugins/specweave-github/lib/github-ac-comment-poster.js.map +1 -1
- package/dist/plugins/specweave-github/lib/github-feature-sync-cli.js +6 -0
- package/dist/plugins/specweave-github/lib/github-feature-sync-cli.js.map +1 -1
- package/dist/plugins/specweave-github/lib/github-feature-sync.d.ts +36 -1
- package/dist/plugins/specweave-github/lib/github-feature-sync.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/github-feature-sync.js +266 -5
- package/dist/plugins/specweave-github/lib/github-feature-sync.js.map +1 -1
- package/dist/plugins/specweave-github/lib/user-story-content-builder.d.ts +2 -1
- package/dist/plugins/specweave-github/lib/user-story-content-builder.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/user-story-content-builder.js +6 -4
- package/dist/plugins/specweave-github/lib/user-story-content-builder.js.map +1 -1
- package/dist/plugins/specweave-github/lib/user-story-issue-builder.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/user-story-issue-builder.js +37 -17
- package/dist/plugins/specweave-github/lib/user-story-issue-builder.js.map +1 -1
- package/dist/src/cli/commands/refresh-plugins.d.ts.map +1 -1
- package/dist/src/cli/commands/refresh-plugins.js +9 -0
- package/dist/src/cli/commands/refresh-plugins.js.map +1 -1
- package/dist/src/cli/commands/sync-progress.d.ts.map +1 -1
- package/dist/src/cli/commands/sync-progress.js +72 -2
- package/dist/src/cli/commands/sync-progress.js.map +1 -1
- package/dist/src/config/types.d.ts +2 -2
- package/dist/src/core/increment/increment-utils.d.ts +27 -4
- package/dist/src/core/increment/increment-utils.d.ts.map +1 -1
- package/dist/src/core/increment/increment-utils.js +44 -17
- package/dist/src/core/increment/increment-utils.js.map +1 -1
- package/dist/src/core/increment/template-creator.d.ts +26 -0
- package/dist/src/core/increment/template-creator.d.ts.map +1 -1
- package/dist/src/core/increment/template-creator.js +179 -20
- package/dist/src/core/increment/template-creator.js.map +1 -1
- package/dist/src/importers/import-to-increment.d.ts +111 -0
- package/dist/src/importers/import-to-increment.d.ts.map +1 -0
- package/dist/src/importers/import-to-increment.js +223 -0
- package/dist/src/importers/import-to-increment.js.map +1 -0
- package/dist/src/importers/increment-external-ref-detector.d.ts +78 -0
- package/dist/src/importers/increment-external-ref-detector.d.ts.map +1 -0
- package/dist/src/importers/increment-external-ref-detector.js +130 -0
- package/dist/src/importers/increment-external-ref-detector.js.map +1 -0
- package/dist/src/init/research/types.d.ts +1 -1
- package/dist/src/sync/external-issue-auto-creator.d.ts.map +1 -1
- package/dist/src/sync/external-issue-auto-creator.js +28 -1
- package/dist/src/sync/external-issue-auto-creator.js.map +1 -1
- package/dist/src/sync/sync-coordinator.d.ts +6 -0
- package/dist/src/sync/sync-coordinator.d.ts.map +1 -1
- package/dist/src/sync/sync-coordinator.js +42 -2
- package/dist/src/sync/sync-coordinator.js.map +1 -1
- package/package.json +1 -1
- package/plugins/specweave/hooks/lib/update-active-increment.sh +2 -2
- package/plugins/specweave/hooks/lib/update-status-line.sh +2 -2
- package/plugins/specweave/hooks/stop-auto-v5.sh +28 -8
- package/plugins/specweave/hooks/stop-sync.sh +10 -5
- package/plugins/specweave/hooks/universal/fail-fast-wrapper.sh +8 -4
- package/plugins/specweave/hooks/user-prompt-submit.sh +130 -112
- package/plugins/specweave/hooks/v2/handlers/github-sync-handler.sh +6 -3
- package/plugins/specweave/hooks/v2/handlers/project-bridge-handler.sh +4 -3
- package/plugins/specweave/skills/auto/SKILL.md +5 -3
- package/plugins/specweave/skills/done/SKILL.md +9 -3
- package/plugins/specweave/skills/import/SKILL.md +186 -0
- package/plugins/specweave/skills/increment/SKILL.md +30 -16
- package/plugins/specweave/skills/pm/SKILL.md +29 -2
- package/plugins/specweave/skills/pm/phases/00-deep-interview.md +12 -0
- package/plugins/specweave/skills/team-lead/SKILL.md +4 -2
- package/plugins/specweave/skills/team-merge/SKILL.md +2 -2
- package/plugins/specweave-github/lib/github-ac-comment-poster.js +31 -19
- package/plugins/specweave-github/lib/github-ac-comment-poster.ts +44 -27
- package/plugins/specweave-github/lib/github-feature-sync-cli.js +5 -0
- package/plugins/specweave-github/lib/github-feature-sync-cli.ts +7 -1
- package/plugins/specweave-github/lib/github-feature-sync.js +274 -6
- package/plugins/specweave-github/lib/github-feature-sync.ts +353 -5
- package/plugins/specweave-github/lib/user-story-content-builder.js +6 -4
- package/plugins/specweave-github/lib/user-story-content-builder.ts +6 -4
- package/plugins/specweave-github/lib/user-story-issue-builder.js +26 -11
- package/plugins/specweave-github/lib/user-story-issue-builder.ts +37 -19
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Import external issues from GitHub, Jira, or Azure DevOps and create SpecWeave increments with platform suffixes (G/J/A). Supports filtering and duplicate prevention. Use when saying "import issues", "pull from github", "grab jira issues", or "import from ado".
|
|
3
|
+
argument-hint: "[platform] [filter-query]"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# External Issue Import
|
|
7
|
+
|
|
8
|
+
Import issues from external trackers (GitHub, JIRA, Azure DevOps) and create SpecWeave increments with platform-specific suffixes: **G** (GitHub), **J** (JIRA), **A** (ADO).
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Workflow
|
|
13
|
+
|
|
14
|
+
### STEP 1: Load Configuration
|
|
15
|
+
|
|
16
|
+
1. Read `.specweave/config.json` — check `sync` section
|
|
17
|
+
2. Identify which platforms are configured (`sync.github`, `sync.jira`, `sync.ado`)
|
|
18
|
+
3. If NO platforms configured:
|
|
19
|
+
- Tell user: "No external tools configured. Run `/sw:sync-setup` to connect GitHub, JIRA, or ADO."
|
|
20
|
+
- **STOP**
|
|
21
|
+
|
|
22
|
+
### STEP 2: Platform Selection
|
|
23
|
+
|
|
24
|
+
1. If user specified a platform in the command argument (e.g., `/sw:import github`), use that
|
|
25
|
+
2. If multiple platforms configured and none specified, ask user which to import from:
|
|
26
|
+
- Use AskUserQuestion with configured platforms as options
|
|
27
|
+
3. Validate the selected platform is configured and has credentials
|
|
28
|
+
|
|
29
|
+
### STEP 3: Filter Configuration
|
|
30
|
+
|
|
31
|
+
Ask user for optional filters (or parse from arguments):
|
|
32
|
+
|
|
33
|
+
- **Status**: open (default), closed, all
|
|
34
|
+
- **Labels**: comma-separated label filter
|
|
35
|
+
- **Date range**: last N months (default: 3)
|
|
36
|
+
- **Milestone/Epic**: filter by milestone or epic
|
|
37
|
+
- **Search query**: text search in title/description
|
|
38
|
+
- **Max items**: limit results (default: 20)
|
|
39
|
+
|
|
40
|
+
If user provides no filters, use defaults: open issues, last 3 months, max 20.
|
|
41
|
+
|
|
42
|
+
### STEP 4: Fetch External Issues
|
|
43
|
+
|
|
44
|
+
1. Read credentials from `.env` or environment:
|
|
45
|
+
- GitHub: `GITHUB_TOKEN` or `gh auth status`
|
|
46
|
+
- JIRA: `JIRA_EMAIL` + `JIRA_API_TOKEN` + domain from config
|
|
47
|
+
- ADO: `ADO_PAT` + org/project from config
|
|
48
|
+
|
|
49
|
+
2. Use the platform's API to fetch issues matching filters:
|
|
50
|
+
- **GitHub**: `gh api repos/{owner}/{repo}/issues` with query params
|
|
51
|
+
- **JIRA**: JIRA REST API v3 with JQL query
|
|
52
|
+
- **ADO**: ADO REST API with WIQL query
|
|
53
|
+
|
|
54
|
+
3. Parse results into a display-friendly list
|
|
55
|
+
|
|
56
|
+
### STEP 5: Display and Select
|
|
57
|
+
|
|
58
|
+
Present issues in a numbered table:
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
# | ID | Title | Status | Priority | Labels
|
|
62
|
+
--|-----------|--------------------------------|--------|----------|--------
|
|
63
|
+
1 | #123 | Fix login redirect loop | open | P1 | bug
|
|
64
|
+
2 | #456 | Add dark mode support | open | P2 | feature
|
|
65
|
+
3 | #789 | Update API documentation | open | P3 | docs
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Ask user to select which issues to import:
|
|
69
|
+
- Single: "1"
|
|
70
|
+
- Multiple: "1,3,5"
|
|
71
|
+
- All: "all"
|
|
72
|
+
- Range: "1-5"
|
|
73
|
+
|
|
74
|
+
### STEP 6: Duplicate Detection
|
|
75
|
+
|
|
76
|
+
For each selected issue, check if already imported:
|
|
77
|
+
|
|
78
|
+
1. Generate the canonical `external_ref` string:
|
|
79
|
+
- GitHub: `github#{owner}/{repo}#{issue_number}`
|
|
80
|
+
- JIRA: `jira#{project_key}#{issue_key}`
|
|
81
|
+
- ADO: `ado#{org}/{project}#{work_item_id}`
|
|
82
|
+
|
|
83
|
+
2. Scan ALL `.specweave/increments/**/metadata.json` files for matching `external_ref`
|
|
84
|
+
- Check: active, _archive, _abandoned, _paused directories
|
|
85
|
+
|
|
86
|
+
3. For duplicates found:
|
|
87
|
+
- Report: "Skipping #{issue_id} — already imported as {increment_id}"
|
|
88
|
+
- Remove from selection
|
|
89
|
+
|
|
90
|
+
### STEP 7: Create Increments
|
|
91
|
+
|
|
92
|
+
For each non-duplicate selected issue:
|
|
93
|
+
|
|
94
|
+
1. **Generate increment ID** with platform suffix:
|
|
95
|
+
- GitHub issue #123 "fix-login-bug" → `0271G-fix-login-bug`
|
|
96
|
+
- JIRA PROJ-456 "payment-flow" → `0272J-payment-flow`
|
|
97
|
+
- ADO #789 "ci-pipeline" → `0273A-ci-pipeline`
|
|
98
|
+
|
|
99
|
+
2. **Create increment files** via `createIncrementTemplates()` with `externalSource`:
|
|
100
|
+
- `metadata.json` — includes `external_ref`, `origin: "external"`, `source_platform`
|
|
101
|
+
- `spec.md` — pre-filled with issue title, description, and acceptance criteria
|
|
102
|
+
- `plan.md` — template (to be completed via architect skill)
|
|
103
|
+
- `tasks.md` — derived from acceptance criteria if available, template otherwise
|
|
104
|
+
|
|
105
|
+
3. **Map priority**: Use external priority if available, default to P2
|
|
106
|
+
4. **Map type**: bug → bug, feature/epic/story → feature
|
|
107
|
+
|
|
108
|
+
### STEP 8: Post-Import Summary
|
|
109
|
+
|
|
110
|
+
Display results:
|
|
111
|
+
|
|
112
|
+
```
|
|
113
|
+
Import Complete
|
|
114
|
+
===============
|
|
115
|
+
|
|
116
|
+
Created:
|
|
117
|
+
- 0271G-fix-login-bug (from GitHub #123)
|
|
118
|
+
- 0273A-ci-pipeline (from ADO #789)
|
|
119
|
+
|
|
120
|
+
Skipped (duplicates):
|
|
121
|
+
- GitHub #456 — already imported as 0200G-dark-mode
|
|
122
|
+
|
|
123
|
+
Errors: none
|
|
124
|
+
|
|
125
|
+
Next steps:
|
|
126
|
+
- /sw:do 0271G — Start working on first import
|
|
127
|
+
- /sw:auto 0271G — Run autonomously
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## Platform Suffix Reference
|
|
133
|
+
|
|
134
|
+
| Platform | Suffix | Example |
|
|
135
|
+
|----------|--------|---------|
|
|
136
|
+
| GitHub | G | `0271G-fix-login-bug` |
|
|
137
|
+
| JIRA | J | `0272J-payment-flow` |
|
|
138
|
+
| ADO | A | `0273A-ci-pipeline` |
|
|
139
|
+
| Legacy | E | `0111E-old-import` (backwards compat) |
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Edge Cases
|
|
144
|
+
|
|
145
|
+
### No issues found
|
|
146
|
+
Tell user "No matching issues found. Try adjusting filters." Suggest broader search.
|
|
147
|
+
|
|
148
|
+
### External tool API error
|
|
149
|
+
Report the error clearly. Suggest checking credentials: "Run `/sw:sync-setup` to verify credentials."
|
|
150
|
+
|
|
151
|
+
### Issue has no description
|
|
152
|
+
Create spec with title only and mark as needs-review.
|
|
153
|
+
|
|
154
|
+
### Issue has no acceptance criteria
|
|
155
|
+
Create template-style tasks.md with placeholder tasks.
|
|
156
|
+
|
|
157
|
+
### Rate limiting
|
|
158
|
+
Report rate limit and suggest waiting or reducing the import batch size.
|
|
159
|
+
|
|
160
|
+
### Umbrella / multi-repo project
|
|
161
|
+
If in an umbrella project with multiple repos under `repositories/`, ask which repo's `.specweave/` should receive the increment.
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## Configuration Reference
|
|
166
|
+
|
|
167
|
+
Required in `.specweave/config.json`:
|
|
168
|
+
|
|
169
|
+
```json
|
|
170
|
+
{
|
|
171
|
+
"sync": {
|
|
172
|
+
"enabled": true,
|
|
173
|
+
"github": { "enabled": true, "owner": "...", "repo": "..." },
|
|
174
|
+
"jira": { "enabled": true, "domain": "...", "projectKey": "..." },
|
|
175
|
+
"ado": { "enabled": true, "organization": "...", "project": "..." }
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Credentials in `.env` (never committed):
|
|
181
|
+
```
|
|
182
|
+
GITHUB_TOKEN=ghp_...
|
|
183
|
+
JIRA_EMAIL=user@example.com
|
|
184
|
+
JIRA_API_TOKEN=...
|
|
185
|
+
ADO_PAT=...
|
|
186
|
+
```
|
|
@@ -46,15 +46,20 @@ Increment planning produces specs, plans, and task breakdowns that require user
|
|
|
46
46
|
STEP 0A: Discipline Check (BLOCKING)
|
|
47
47
|
STEP 0B: WIP Enforcement
|
|
48
48
|
STEP 0C: Tech Stack Detection
|
|
49
|
-
STEP 1: Pre-flight (TDD mode, multi-project, Deep Interview)
|
|
50
|
-
STEP 1a: Deep Interview (if enabled)
|
|
49
|
+
STEP 1: Pre-flight (TDD mode, multi-project, Deep Interview check)
|
|
51
50
|
STEP 2: Project Context (resolve project/board)
|
|
52
|
-
STEP 3: Create Increment (via Template API)
|
|
51
|
+
STEP 3: Create Increment (via Template API) ← folder + ID exist after this
|
|
52
|
+
STEP 3a: Deep Interview (if enabled) ← runs AFTER folder exists
|
|
53
53
|
STEP 4: Delegation (architect + test-aware-planner)
|
|
54
54
|
STEP 5: Post-Creation Sync
|
|
55
55
|
STEP 6: Execution Strategy Recommendation
|
|
56
56
|
```
|
|
57
57
|
|
|
58
|
+
**CRITICAL**: Step 3 (Create Increment) MUST run before Step 3a (Deep Interview).
|
|
59
|
+
The interview state file is written to `.specweave/state/interview-{increment-id}.json`,
|
|
60
|
+
and the enforcement guard looks for it by increment ID. If the interview runs before the
|
|
61
|
+
increment folder exists, the guard cannot find the state file and blocks spec.md writing.
|
|
62
|
+
|
|
58
63
|
## Step 0A: Discipline Check (MANDATORY)
|
|
59
64
|
|
|
60
65
|
**Cannot start N+1 until N is DONE.**
|
|
@@ -120,24 +125,13 @@ jq -r '.testing.defaultTestMode // "test-after"' .specweave/config.json 2>/dev/n
|
|
|
120
125
|
# 2. Check multi-project config
|
|
121
126
|
specweave context projects 2>/dev/null
|
|
122
127
|
|
|
123
|
-
# 3. Check deep interview mode
|
|
124
|
-
jq -r '.planning.deepInterview.enabled // false' .specweave/config.json 2>/dev/null
|
|
128
|
+
# 3. Check deep interview mode (note: interview itself runs at Step 3a, after increment exists)
|
|
129
|
+
DEEP_INTERVIEW=$(jq -r '.planning.deepInterview.enabled // false' .specweave/config.json 2>/dev/null)
|
|
125
130
|
|
|
126
131
|
# 4. Check WIP limits
|
|
127
132
|
find .specweave/increments -maxdepth 2 -name "metadata.json" -exec grep -l '"status":"active"' {} \; 2>/dev/null | wc -l
|
|
128
133
|
```
|
|
129
134
|
|
|
130
|
-
## Step 1a: Deep Interview Mode (if enabled)
|
|
131
|
-
|
|
132
|
-
**If deep interview is enabled, delegate to PM skill:**
|
|
133
|
-
|
|
134
|
-
```typescript
|
|
135
|
-
Skill({ skill: "sw:pm", args: "Deep interview mode for: <user description>" })
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
**THINK about complexity first** - assess before asking:
|
|
139
|
-
- Trivial: 0-3 questions | Small: 4-8 | Medium: 9-18 | Large: 19-40+
|
|
140
|
-
|
|
141
135
|
## Step 2: Project Context
|
|
142
136
|
|
|
143
137
|
```bash
|
|
@@ -246,6 +240,26 @@ Create files in order: metadata.json FIRST, then spec.md, plan.md, tasks.md.
|
|
|
246
240
|
4. **Increment naming** - Format: `####-descriptive-kebab-case`
|
|
247
241
|
5. **Multi-repo** - In umbrella projects with `repositories/` folder, create increments in EACH repo's `.specweave/`, not the umbrella root
|
|
248
242
|
|
|
243
|
+
## Step 3a: Deep Interview Mode (if enabled)
|
|
244
|
+
|
|
245
|
+
**IMPORTANT**: This step runs AFTER the increment folder is created (Step 3), so the
|
|
246
|
+
interview state file can reference the real increment ID.
|
|
247
|
+
|
|
248
|
+
**If deep interview is enabled, delegate to PM skill:**
|
|
249
|
+
|
|
250
|
+
```typescript
|
|
251
|
+
Skill({ skill: "sw:pm", args: "Deep interview for increment XXXX-name: <user description>" })
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
The PM skill will:
|
|
255
|
+
1. Assess complexity and determine question count (trivial: 0-3, small: 4-8, medium: 9-18, large: 19-40)
|
|
256
|
+
2. Interview the user across relevant categories
|
|
257
|
+
3. Write interview state to `.specweave/state/interview-{increment-id}.json`
|
|
258
|
+
4. Return interview summary for spec.md creation
|
|
259
|
+
|
|
260
|
+
**After PM returns**, read the interview state file to confirm all categories are covered
|
|
261
|
+
before proceeding to spec.md creation (especially when `enforcement: "strict"`).
|
|
262
|
+
|
|
249
263
|
## Step 4: Delegation
|
|
250
264
|
|
|
251
265
|
After increment creation:
|
|
@@ -44,8 +44,35 @@ If `true`:
|
|
|
44
44
|
- Small features: 4-8 questions
|
|
45
45
|
- Medium features: 9-18 questions
|
|
46
46
|
- Large features: 19-40 questions
|
|
47
|
-
3.
|
|
48
|
-
|
|
47
|
+
3. Check `minQuestions` config: `jq -r '.planning.deepInterview.minQuestions // 5' .specweave/config.json`
|
|
48
|
+
- If complexity assessment yields fewer questions than minQuestions, use minQuestions as the floor
|
|
49
|
+
4. Cover relevant categories (skip those that don't apply)
|
|
50
|
+
5. Only proceed to Research phase after sufficient clarity
|
|
51
|
+
|
|
52
|
+
### Writing Interview State to Disk (CRITICAL)
|
|
53
|
+
|
|
54
|
+
**This skill runs with `context: fork` (isolated LLM context), but file writes persist.**
|
|
55
|
+
|
|
56
|
+
When invoked from `sw:increment` with an increment ID (e.g., "Deep interview for increment 0266-foo: ..."),
|
|
57
|
+
you MUST write the interview state file to disk so the enforcement guard can find it:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
# Extract increment ID from the args (e.g., "Deep interview for increment 0266-foo: ...")
|
|
61
|
+
# Initialize interview state file BEFORE starting questions
|
|
62
|
+
mkdir -p .specweave/state
|
|
63
|
+
echo '{"incrementId":"XXXX-name","startedAt":"'$(date -Iseconds)'","coveredCategories":{}}' \
|
|
64
|
+
> .specweave/state/interview-XXXX-name.json
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
After covering each category, update the state file:
|
|
68
|
+
```bash
|
|
69
|
+
jq '.coveredCategories.architecture = {"coveredAt": "'$(date -Iseconds)'", "summary": "..."}' \
|
|
70
|
+
.specweave/state/interview-XXXX-name.json > tmp && mv tmp .specweave/state/interview-XXXX-name.json
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
**Why this matters**: The `interview-enforcement-guard.sh` (PreToolUse hook on Write) checks
|
|
74
|
+
`.specweave/state/interview-{increment-id}.json` before allowing spec.md writes. If this file
|
|
75
|
+
is missing or incomplete, spec.md creation is BLOCKED in strict mode.
|
|
49
76
|
|
|
50
77
|
## Core Principles
|
|
51
78
|
|
|
@@ -207,6 +207,18 @@ When factors point to different complexity levels:
|
|
|
207
207
|
| **Medium** | 9-18 questions | Multiple components, some integration points |
|
|
208
208
|
| **Large** | 19-40 questions | Architectural, cross-cutting, high-risk (payments, security) |
|
|
209
209
|
|
|
210
|
+
### Config Floor: minQuestions
|
|
211
|
+
|
|
212
|
+
The project may define a minimum question count in config:
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
jq -r '.planning.deepInterview.minQuestions // 5' .specweave/config.json 2>/dev/null
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
**Rule**: If your complexity assessment yields fewer questions than `minQuestions`, use `minQuestions` as the floor. For example, if you assess a feature as "trivial" (0-3 questions) but `minQuestions` is 5, ask at least 5 questions.
|
|
219
|
+
|
|
220
|
+
This prevents teams from accidentally under-interviewing when they have set an organizational minimum.
|
|
221
|
+
|
|
210
222
|
### What's a "Single Component"?
|
|
211
223
|
|
|
212
224
|
A "single component" means **isolated scope** - changes contained to one area:
|
|
@@ -664,8 +664,10 @@ Orchestrator Final Check:
|
|
|
664
664
|
2. No unresolved BLOCKING_ISSUE messages
|
|
665
665
|
3. Run full test suite (all domains combined)
|
|
666
666
|
4. Run /sw:grill on the combined increment
|
|
667
|
-
5.
|
|
668
|
-
6. If
|
|
667
|
+
5. Run /sw:done --auto <id> for each increment in dependency order
|
|
668
|
+
6. If any /sw:done --auto fails, report the failure and continue with remaining increments
|
|
669
|
+
7. If all pass -> /sw:team-merge
|
|
670
|
+
8. If failures -> identify owning agent, send fix request via SendMessage
|
|
669
671
|
```
|
|
670
672
|
|
|
671
673
|
### Grill Checklist per Domain
|
|
@@ -71,8 +71,8 @@ Closure order respects contract chain:
|
|
|
71
71
|
For each teammate's increment, in dependency order:
|
|
72
72
|
|
|
73
73
|
```bash
|
|
74
|
-
# Run /sw:done per increment -- triggers quality gates
|
|
75
|
-
/sw:done <increment-id>
|
|
74
|
+
# Run /sw:done --auto per increment -- triggers quality gates, skips user confirmation
|
|
75
|
+
/sw:done <increment-id> --auto
|
|
76
76
|
```
|
|
77
77
|
|
|
78
78
|
This ensures:
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { readFile } from "fs/promises";
|
|
2
|
+
import { existsSync } from "fs";
|
|
3
|
+
import * as path from "path";
|
|
2
4
|
import { execFileNoThrow } from "../../../src/utils/execFileNoThrow.js";
|
|
3
5
|
import { pushSyncUserStories } from "./github-push-sync.js";
|
|
4
6
|
async function postACProgressComments(incrementId, affectedUSIds, specPath, options) {
|
|
@@ -16,7 +18,7 @@ async function postACProgressComments(incrementId, affectedUSIds, specPath, opti
|
|
|
16
18
|
});
|
|
17
19
|
return result;
|
|
18
20
|
}
|
|
19
|
-
const issueLinks = parseIssueLinks(
|
|
21
|
+
const issueLinks = await parseIssueLinks(specPath);
|
|
20
22
|
const repoSlug = `${options.owner}/${options.repo}`;
|
|
21
23
|
const env = options.token ? { GH_TOKEN: options.token } : void 0;
|
|
22
24
|
for (const usId of affectedUSIds) {
|
|
@@ -53,26 +55,36 @@ async function postACProgressComments(incrementId, affectedUSIds, specPath, opti
|
|
|
53
55
|
}
|
|
54
56
|
return result;
|
|
55
57
|
}
|
|
56
|
-
function parseIssueLinks(
|
|
58
|
+
async function parseIssueLinks(specPath) {
|
|
57
59
|
const links = {};
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
60
|
+
try {
|
|
61
|
+
const metadataPath = path.join(path.dirname(specPath), "metadata.json");
|
|
62
|
+
if (!existsSync(metadataPath)) return links;
|
|
63
|
+
const raw = await readFile(metadataPath, "utf-8");
|
|
64
|
+
const metadata = JSON.parse(raw);
|
|
65
|
+
if (metadata.github?.issues && Array.isArray(metadata.github.issues)) {
|
|
66
|
+
for (const entry of metadata.github.issues) {
|
|
67
|
+
if (entry.userStory && entry.number) {
|
|
68
|
+
links[entry.userStory] = {
|
|
69
|
+
issueNumber: entry.number,
|
|
70
|
+
issueUrl: entry.url || ""
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (metadata.externalLinks?.github?.issues) {
|
|
76
|
+
const issues = metadata.externalLinks.github.issues;
|
|
77
|
+
for (const [usId, data] of Object.entries(issues)) {
|
|
78
|
+
const issueData = data;
|
|
79
|
+
if (issueData.issueNumber) {
|
|
80
|
+
links[usId] = {
|
|
81
|
+
issueNumber: issueData.issueNumber,
|
|
82
|
+
issueUrl: issueData.issueUrl || ""
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
}
|
|
75
86
|
}
|
|
87
|
+
} catch {
|
|
76
88
|
}
|
|
77
89
|
return links;
|
|
78
90
|
}
|
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { readFile } from 'fs/promises';
|
|
12
|
+
import { existsSync } from 'fs';
|
|
13
|
+
import * as path from 'path';
|
|
12
14
|
import { execFileNoThrow } from '../../../src/utils/execFileNoThrow.js';
|
|
13
15
|
import { pushSyncUserStories } from './github-push-sync.js';
|
|
14
16
|
import type { UserStoryForSync } from './github-push-sync.js';
|
|
@@ -69,7 +71,7 @@ export async function postACProgressComments(
|
|
|
69
71
|
return result;
|
|
70
72
|
}
|
|
71
73
|
|
|
72
|
-
const issueLinks = parseIssueLinks(
|
|
74
|
+
const issueLinks = await parseIssueLinks(specPath);
|
|
73
75
|
const repoSlug = `${options.owner}/${options.repo}`;
|
|
74
76
|
const env = options.token ? { GH_TOKEN: options.token } : undefined;
|
|
75
77
|
|
|
@@ -116,37 +118,52 @@ export async function postACProgressComments(
|
|
|
116
118
|
}
|
|
117
119
|
|
|
118
120
|
/**
|
|
119
|
-
* Parse issue links from
|
|
120
|
-
*
|
|
121
|
+
* Parse issue links from metadata.json (sibling of spec.md).
|
|
122
|
+
*
|
|
123
|
+
* Supports TWO formats:
|
|
124
|
+
* - OLD: metadata.github.issues[] with { userStory, number, url }
|
|
125
|
+
* - NEW: metadata.externalLinks.github.issues with { [US-XXX]: { issueNumber, issueUrl } }
|
|
126
|
+
*
|
|
127
|
+
* Falls back to empty if metadata.json is missing or invalid.
|
|
121
128
|
*/
|
|
122
|
-
function parseIssueLinks(
|
|
129
|
+
async function parseIssueLinks(specPath: string): Promise<Record<string, ParsedUSIssueLink>> {
|
|
123
130
|
const links: Record<string, ParsedUSIssueLink> = {};
|
|
124
131
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
132
|
+
try {
|
|
133
|
+
const metadataPath = path.join(path.dirname(specPath), 'metadata.json');
|
|
134
|
+
if (!existsSync(metadataPath)) return links;
|
|
135
|
+
|
|
136
|
+
const raw = await readFile(metadataPath, 'utf-8');
|
|
137
|
+
const metadata = JSON.parse(raw);
|
|
138
|
+
|
|
139
|
+
// OLD format: metadata.github.issues[] array
|
|
140
|
+
if (metadata.github?.issues && Array.isArray(metadata.github.issues)) {
|
|
141
|
+
for (const entry of metadata.github.issues) {
|
|
142
|
+
if (entry.userStory && entry.number) {
|
|
143
|
+
links[entry.userStory] = {
|
|
144
|
+
issueNumber: entry.number,
|
|
145
|
+
issueUrl: entry.url || '',
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
143
150
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
151
|
+
// NEW format: metadata.externalLinks.github.issues object
|
|
152
|
+
if (metadata.externalLinks?.github?.issues) {
|
|
153
|
+
const issues = metadata.externalLinks.github.issues;
|
|
154
|
+
for (const [usId, data] of Object.entries(issues)) {
|
|
155
|
+
const issueData = data as { issueNumber?: number; issueUrl?: string };
|
|
156
|
+
if (issueData.issueNumber) {
|
|
157
|
+
// NEW format entries override OLD format for the same US
|
|
158
|
+
links[usId] = {
|
|
159
|
+
issueNumber: issueData.issueNumber,
|
|
160
|
+
issueUrl: issueData.issueUrl || '',
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
}
|
|
149
164
|
}
|
|
165
|
+
} catch {
|
|
166
|
+
// Graceful fallback: return empty if metadata.json is missing or invalid
|
|
150
167
|
}
|
|
151
168
|
|
|
152
169
|
return links;
|
|
@@ -135,12 +135,17 @@ async function main() {
|
|
|
135
135
|
}
|
|
136
136
|
process.exit(0);
|
|
137
137
|
} catch (error) {
|
|
138
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
139
|
+
console.log(`
|
|
140
|
+
[ERROR] Sync failed for ${featureId}: ${msg}`);
|
|
138
141
|
console.error(`
|
|
139
142
|
\u274C Sync failed:`, error);
|
|
140
143
|
process.exit(1);
|
|
141
144
|
}
|
|
142
145
|
}
|
|
143
146
|
main().catch((error) => {
|
|
147
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
148
|
+
console.log(`[FATAL] ${msg}`);
|
|
144
149
|
console.error("Fatal error:", error);
|
|
145
150
|
process.exit(1);
|
|
146
151
|
});
|
|
@@ -195,7 +195,11 @@ async function main() {
|
|
|
195
195
|
}
|
|
196
196
|
|
|
197
197
|
process.exit(0);
|
|
198
|
-
} catch (error) {
|
|
198
|
+
} catch (error: unknown) {
|
|
199
|
+
// FIXED (v1.0.302): Write errors to stdout too, since stderr may be suppressed
|
|
200
|
+
// by run_with_timeout() in shell handlers. This ensures errors appear in throttle.log.
|
|
201
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
202
|
+
console.log(`\n[ERROR] Sync failed for ${featureId}: ${msg}`);
|
|
199
203
|
console.error(`\n❌ Sync failed:`, error);
|
|
200
204
|
process.exit(1);
|
|
201
205
|
}
|
|
@@ -203,6 +207,8 @@ async function main() {
|
|
|
203
207
|
|
|
204
208
|
// Run CLI
|
|
205
209
|
main().catch(error => {
|
|
210
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
211
|
+
console.log(`[FATAL] ${msg}`);
|
|
206
212
|
console.error('Fatal error:', error);
|
|
207
213
|
process.exit(1);
|
|
208
214
|
});
|