@pareto4/pp-core 1.7.1 → 1.8.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/.claude-plugin/plugin.json +1 -1
- package/bin/install.js +116 -0
- package/commands/pp-build-handoff.md +8 -2
- package/commands/pp-discovery-to-build.md +40 -0
- package/gemini-extension.json +1 -1
- package/package.json +2 -1
- package/pareto-skills/_shared/create-project-board.sh +195 -0
- package/pareto-skills/_shared/pp-claude-md-protocol.md +306 -0
- package/pareto-skills/_shared/sync-phase-status.sh +139 -0
- package/pareto-skills/pp-brand/SKILL.md +644 -0
- package/pareto-skills/pp-brand/assets/crew/ParetoAgent.inline.html +120 -0
- package/pareto-skills/pp-brand/assets/crew/ParetoAgent.tsx +239 -0
- package/pareto-skills/pp-brand/assets/crew/build.svg +11 -0
- package/pareto-skills/pp-brand/assets/crew/deploy.svg +11 -0
- package/pareto-skills/pp-brand/assets/crew/discovery.svg +11 -0
- package/pareto-skills/pp-brand/assets/crew/plan.svg +11 -0
- package/pareto-skills/pp-brand/assets/crew/run.svg +11 -0
- package/pareto-skills/pp-brand/assets/crew/validate.svg +11 -0
- package/pareto-skills/pp-brand/assets/pareto-brand-preview.html +547 -0
- package/pareto-skills/pp-obsidian-notes/SKILL.md +127 -0
- package/pareto-skills/pp-obsidian-notes/templates/CLAUDE.md +29 -0
- package/pareto-skills/pp-obsidian-notes/templates/client-context.md +116 -0
- package/pareto-skills/pp-obsidian-notes/templates/meeting-note.md +66 -0
- package/pareto-skills/pp-obsidian-notes/templates/pareto-context.md +102 -0
- package/pareto-skills/pp-obsidian-notes/templates/proposal.md +117 -0
- package/pareto-skills/pp-project-setup/SKILL.md +1364 -0
- package/pp-core/bin/lib/intel.cjs +1 -1
- package/pp-core/bin/lib/io.cjs +120 -0
- package/pp-core/bin/lib/profile-pipeline-command-router.cjs +1 -1
- package/pp-core/bin/lib/roadmap-parser.cjs +1 -1
- package/pp-core/bin/lib/runtime-artifact-conversion.cjs +2 -2
- package/pp-core/bin/lib/surface.cjs +1 -1
- package/pp-core/bin/lib/worktree-safety.cjs +2 -2
- package/pp-core/bin/pp-tools.cjs +23 -0
- package/pp-core/references/ui-brand.md +73 -11
- package/pp-core/templates/codebase/structure.md +5 -5
- package/pp-core/templates/spec.md +7 -7
- package/pp-core/workflows/add-backlog.md +1 -1
- package/pp-core/workflows/autonomous.md +4 -4
- package/pp-core/workflows/debug.md +1 -1
- package/pp-core/workflows/{build-handoff.md → discovery-to-build.md} +2 -2
- package/pp-core/workflows/execute-phase.md +9 -1
- package/pp-core/workflows/plan-phase.md +14 -12
- package/pp-core/workflows/pr-branch.md +5 -4
- package/pp-core/workflows/reapply-patches.md +1 -1
- package/pp-core/workflows/ship.md +1 -1
- package/pp-core/workflows/thread.md +1 -1
- package/pp-core/workflows/verify-work.md +6 -5
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
pp-claude-md-protocol.md
|
|
3
|
+
|
|
4
|
+
Canonical pp-core ↔ GitHub protocol injected into a project's CLAUDE.md.
|
|
5
|
+
|
|
6
|
+
Used by:
|
|
7
|
+
- pareto-build-handoff (Step 6b: CLAUDE.md update)
|
|
8
|
+
- pareto-push-issues (Step 5.5: CLAUDE.md injection)
|
|
9
|
+
|
|
10
|
+
To inject: append the block below `## GitHub Board Sync` (if a section of
|
|
11
|
+
that name already exists, replace it — idempotent).
|
|
12
|
+
|
|
13
|
+
Single source of truth — edit here, not in the skills. Every repo that
|
|
14
|
+
runs build-handoff or push-issues gets the same protocol.
|
|
15
|
+
-->
|
|
16
|
+
|
|
17
|
+
## GitHub Board Sync
|
|
18
|
+
|
|
19
|
+
Requirements are tracked as GitHub issues on a project board. The mapping
|
|
20
|
+
between pp-core requirement IDs (EPIC-xxx, US-xxx, FR-xxx) and GitHub issue
|
|
21
|
+
numbers lives in `.planning/github-issues.json`.
|
|
22
|
+
|
|
23
|
+
**Status flow:**
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
Backlog → Discuss → Planned → In Progress → Verification → PR Raised → Done
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Every phase runs on its own branch and merges to `main` via a pull request
|
|
30
|
+
after verification passes. Stories only close when their phase PR is merged.
|
|
31
|
+
|
|
32
|
+
### Per-phase lifecycle
|
|
33
|
+
|
|
34
|
+
**1. Phase starts (executing begins)**
|
|
35
|
+
|
|
36
|
+
Create a branch from `main` named `phase/{N}-{slug}` (slug from ROADMAP.md
|
|
37
|
+
phase title, kebab-case). All phase commits — code AND `.planning/` artifacts
|
|
38
|
+
— land on this branch.
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
git checkout main && git pull
|
|
42
|
+
git checkout -b "phase/${N}-${SLUG}"
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Move each story issue covered by the phase to **In Progress**:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
gh issue edit {number} --add-label "in-progress,phase:{N}" \
|
|
49
|
+
--remove-label "verification" -R {repo}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Also set the `Phase` custom field on the board item to the phase number.
|
|
53
|
+
|
|
54
|
+
**2. Phase implementation complete (SUMMARY.md written)**
|
|
55
|
+
|
|
56
|
+
Push the branch (first push uses `-u origin HEAD`) and move the stories to
|
|
57
|
+
**Verification**:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
git push -u origin HEAD
|
|
61
|
+
gh issue edit {number} --add-label "verification" \
|
|
62
|
+
--remove-label "in-progress" -R {repo}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**3. Verification passes (VERIFICATION.md result = PASS)**
|
|
66
|
+
|
|
67
|
+
Open a pull request from the phase branch to `main`. The PR body MUST use
|
|
68
|
+
`Closes #N` for every story issue so GitHub auto-closes them on merge.
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
PR_BODY=$(cat <<EOF
|
|
72
|
+
# Phase ${N}: ${PHASE_TITLE}
|
|
73
|
+
|
|
74
|
+
## Summary
|
|
75
|
+
${SUMMARY_FROM_SUMMARY_MD}
|
|
76
|
+
|
|
77
|
+
## Requirements delivered
|
|
78
|
+
Closes #${ISSUE1} — ${US_ID_1}: ${STORY_TITLE_1}
|
|
79
|
+
Closes #${ISSUE2} — ${US_ID_2}: ${STORY_TITLE_2}
|
|
80
|
+
|
|
81
|
+
## Verification
|
|
82
|
+
VERIFICATION.md result: PASS
|
|
83
|
+
- ${KEY_OUTCOMES}
|
|
84
|
+
|
|
85
|
+
## Phase artifacts
|
|
86
|
+
- \`.planning/phases/${N}-${SLUG}/PLAN.md\`
|
|
87
|
+
- \`.planning/phases/${N}-${SLUG}/SUMMARY.md\`
|
|
88
|
+
- \`.planning/phases/${N}-${SLUG}/VERIFICATION.md\`
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
Generated by pp-core phase execution.
|
|
92
|
+
EOF
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
gh pr create \
|
|
96
|
+
--base main \
|
|
97
|
+
--head "phase/${N}-${SLUG}" \
|
|
98
|
+
--title "Phase ${N}: ${PHASE_TITLE}" \
|
|
99
|
+
--body "$PR_BODY" \
|
|
100
|
+
--label "phase:${N}"
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
**Do not close the story issues at this step.** They stay in Verification
|
|
104
|
+
until the PR merges. Keeping them open until merge is what makes the board
|
|
105
|
+
reflect *merged* reality, not just *verified* intent.
|
|
106
|
+
|
|
107
|
+
If verification fails: fix the code on the same phase branch, re-run
|
|
108
|
+
verification, update the PR (it auto-updates from new commits). No new
|
|
109
|
+
branch.
|
|
110
|
+
|
|
111
|
+
**4. PR merged**
|
|
112
|
+
|
|
113
|
+
GitHub auto-closes linked issues. The sync protocol then reconciles:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
# Sync loop — run after merge (or via /pareto-sync-board)
|
|
117
|
+
gh issue edit {number} --remove-label "in-progress,verification" -R {repo}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Update `.planning/github-issues.json`: set each story's `status` to `"Done"`
|
|
121
|
+
and each EPIC's `status` based on whether all its stories are now closed.
|
|
122
|
+
|
|
123
|
+
If the merged phase closed ALL stories under an EPIC, also close the EPIC
|
|
124
|
+
issue with a delivery note:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
gh issue close {epic_number} -R {repo} \
|
|
128
|
+
-c "All stories delivered across phases {list}."
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Status map for .planning/github-issues.json
|
|
132
|
+
|
|
133
|
+
```json
|
|
134
|
+
"status_map": {
|
|
135
|
+
"Backlog": "Backlog",
|
|
136
|
+
"discussing": "Discuss",
|
|
137
|
+
"planned": "Planned",
|
|
138
|
+
"executing": "In Progress",
|
|
139
|
+
"verifying": "Verification",
|
|
140
|
+
"pr_open": "PR Raised",
|
|
141
|
+
"complete": "Done"
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Filtered code-only PRs (optional)
|
|
146
|
+
|
|
147
|
+
If the team wants PR diffs without `.planning/` noise, run `/pp-pr-branch`
|
|
148
|
+
after Step 3 to produce a filtered branch. The filtered branch becomes the
|
|
149
|
+
PR head instead of `phase/{N}-{slug}`. The original phase branch stays as
|
|
150
|
+
the auditable work record.
|
|
151
|
+
|
|
152
|
+
### MANDATORY: Board sync after pp-core phase transitions
|
|
153
|
+
|
|
154
|
+
**These instructions are project-level overrides.** When `.planning/github-issues.json`
|
|
155
|
+
exists, you MUST run the sync script and issue updates at each lifecycle transition
|
|
156
|
+
below. Sync is non-blocking — failures must never stop the pp-core workflow (always
|
|
157
|
+
append `|| true`).
|
|
158
|
+
|
|
159
|
+
**After `/pp-discuss-phase` completes** (CONTEXT.md written):
|
|
160
|
+
|
|
161
|
+
Move stories to **Discuss** and comment on each with key decisions:
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
[ -f ".planning/github-issues.json" ] && bash "$HOME/.claude/skills/pareto/_shared/sync-phase-status.sh" "${PHASE_NUMBER}" "Discuss" 2>&1 || true
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Comment on each story issue for this phase with key decisions that affect it:
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
if [ -f ".planning/github-issues.json" ]; then
|
|
171
|
+
REPO=$(jq -r '.repo' .planning/github-issues.json)
|
|
172
|
+
# For each story in the phase, add a comment with relevant decisions from CONTEXT.md
|
|
173
|
+
jq -r --argjson p "${PHASE_NUMBER}" '.issues | to_entries[] | select(.value.phase == $p and .value.type == "story") | "\(.key) \(.value.number)"' .planning/github-issues.json | while read REQ_ID ISSUE_NUM; do
|
|
174
|
+
gh issue comment "$ISSUE_NUM" -R "$REPO" --body "## Phase ${PHASE_NUMBER} — Context gathered
|
|
175
|
+
|
|
176
|
+
Decisions from \`/pp-discuss-phase\` that affect ${REQ_ID}:
|
|
177
|
+
|
|
178
|
+
${DECISIONS_RELEVANT_TO_THIS_STORY}
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
_Auto-posted by pp-core discuss-phase_" 2>/dev/null || true
|
|
182
|
+
done
|
|
183
|
+
fi
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Replace `${DECISIONS_RELEVANT_TO_THIS_STORY}` with the actual decisions from CONTEXT.md
|
|
187
|
+
that are relevant to each story. Extract from `<decisions>` section.
|
|
188
|
+
|
|
189
|
+
**After `/pp-plan-phase` completes planning** (plans pass all gates, STATE.md updated):
|
|
190
|
+
|
|
191
|
+
Move stories to **Planned** and comment with plan assignment:
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
[ -f ".planning/github-issues.json" ] && bash "$HOME/.claude/skills/pareto/_shared/sync-phase-status.sh" "${PHASE_NUMBER}" "Planned" 2>&1 || true
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Then comment on each story issue with which plan covers it:
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
if [ -f ".planning/github-issues.json" ]; then
|
|
201
|
+
REPO=$(jq -r '.repo' .planning/github-issues.json)
|
|
202
|
+
# For each story, find which PLAN.md references its requirement ID and comment
|
|
203
|
+
jq -r --argjson p "${PHASE_NUMBER}" '.issues | to_entries[] | select(.value.phase == $p and .value.type == "story") | "\(.key) \(.value.number)"' .planning/github-issues.json | while read REQ_ID ISSUE_NUM; do
|
|
204
|
+
gh issue comment "$ISSUE_NUM" -R "$REPO" --body "## Phase ${PHASE_NUMBER} — Planned
|
|
205
|
+
|
|
206
|
+
Covered by: \`${PLAN_FILE}\` (Wave ${WAVE})
|
|
207
|
+
Key tasks: ${TASK_SUMMARY}
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
_Auto-posted by pp-core plan-phase_" 2>/dev/null || true
|
|
211
|
+
done
|
|
212
|
+
fi
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Replace `${PLAN_FILE}`, `${WAVE}`, and `${TASK_SUMMARY}` with actual values from the
|
|
216
|
+
PLAN.md that contains this story's requirement ID in its `requirements` frontmatter field.
|
|
217
|
+
|
|
218
|
+
**After `/pp-execute-phase` begins execution** (branch created, STATE.md shows phase active):
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
[ -f ".planning/github-issues.json" ] && bash "$HOME/.claude/skills/pareto/_shared/sync-phase-status.sh" "${PHASE_NUMBER}" "In Progress" --label-add "in-progress" --label-remove "verification" 2>&1 || true
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
**After `/pp-execute-phase` verification passes** (VERIFICATION.md written, ROADMAP.md updated):
|
|
225
|
+
|
|
226
|
+
Move stories to **Verification**:
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
[ -f ".planning/github-issues.json" ] && bash "$HOME/.claude/skills/pareto/_shared/sync-phase-status.sh" "${PHASE_NUMBER}" "Verification" --label-add "verification" --label-remove "in-progress" 2>&1 || true
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
**After PR is created** (push branch, create PR):
|
|
233
|
+
|
|
234
|
+
Push the branch and create a PR, then move stories to **PR Raised**:
|
|
235
|
+
|
|
236
|
+
```bash
|
|
237
|
+
# 1. Push the phase branch
|
|
238
|
+
git push -u origin HEAD 2>&1 || true
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
```bash
|
|
242
|
+
# 2. Build PR body with Closes links for every story in this phase
|
|
243
|
+
if [ -f ".planning/github-issues.json" ]; then
|
|
244
|
+
REPO=$(jq -r '.repo' .planning/github-issues.json)
|
|
245
|
+
CLOSES_LINES=$(jq -r --argjson p "${PHASE_NUMBER}" '
|
|
246
|
+
.issues | to_entries[]
|
|
247
|
+
| select(.value.phase == $p and .value.type == "story")
|
|
248
|
+
| "Closes #\(.value.number) — \(.key): \(.value.title)"
|
|
249
|
+
' .planning/github-issues.json)
|
|
250
|
+
|
|
251
|
+
gh pr create \
|
|
252
|
+
--base main \
|
|
253
|
+
--head "phase/${PHASE_NUMBER}-${PHASE_SLUG}" \
|
|
254
|
+
--title "Phase ${PHASE_NUMBER}: ${PHASE_NAME}" \
|
|
255
|
+
--body "$(cat <<EOF
|
|
256
|
+
## Summary
|
|
257
|
+
|
|
258
|
+
${SUMMARY_FROM_VERIFICATION_MD}
|
|
259
|
+
|
|
260
|
+
## Requirements delivered
|
|
261
|
+
|
|
262
|
+
${CLOSES_LINES}
|
|
263
|
+
|
|
264
|
+
## Verification
|
|
265
|
+
|
|
266
|
+
VERIFICATION.md result: PASS
|
|
267
|
+
|
|
268
|
+
## Phase artifacts
|
|
269
|
+
|
|
270
|
+
- \`.planning/phases/${PADDED_PHASE}-${PHASE_SLUG}/PLAN.md\`
|
|
271
|
+
- \`.planning/phases/${PADDED_PHASE}-${PHASE_SLUG}/SUMMARY.md\`
|
|
272
|
+
- \`.planning/phases/${PADDED_PHASE}-${PHASE_SLUG}/VERIFICATION.md\`
|
|
273
|
+
|
|
274
|
+
---
|
|
275
|
+
Generated by pp-core phase execution.
|
|
276
|
+
EOF
|
|
277
|
+
)" 2>/dev/null || true
|
|
278
|
+
fi
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
```bash
|
|
282
|
+
# 4. Move stories to PR Raised
|
|
283
|
+
[ -f ".planning/github-issues.json" ] && bash "$HOME/.claude/skills/pareto/_shared/sync-phase-status.sh" "${PHASE_NUMBER}" "PR Raised" 2>&1 || true
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
Stories stay in **PR Raised** until the PR is merged. GitHub auto-closes
|
|
287
|
+
the linked issues via `Closes #N`, which moves them to **Done** on the board.
|
|
288
|
+
|
|
289
|
+
After PR merge, clean up labels:
|
|
290
|
+
|
|
291
|
+
```bash
|
|
292
|
+
[ -f ".planning/github-issues.json" ] && bash "$HOME/.claude/skills/pareto/_shared/sync-phase-status.sh" "${PHASE_NUMBER}" "Done" --label-remove "in-progress,verification" 2>&1 || true
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
If any sync is missed or the board drifts, use the recovery steps below.
|
|
296
|
+
|
|
297
|
+
### Recovery
|
|
298
|
+
|
|
299
|
+
If any step is missed, run `/pareto-sync-board` to reconcile. It:
|
|
300
|
+
- Reads `.planning/STATE.md`, `ROADMAP.md`, and every phase directory
|
|
301
|
+
- Checks `gh pr list --head phase/{N}-*` for PR state
|
|
302
|
+
- Only closes stories whose phase PR shows state `MERGED`
|
|
303
|
+
- Rewrites `github-issues.json` to match live board state
|
|
304
|
+
|
|
305
|
+
If `.planning/github-issues.json` is missing entirely, run
|
|
306
|
+
`/pareto-push-issues` to rebuild it from `.planning/REQUIREMENTS.md`.
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# sync-phase-status.sh
|
|
3
|
+
#
|
|
4
|
+
# Move GitHub issues for a pp-core phase to a target board status.
|
|
5
|
+
# Reads .planning/github-issues.json to find issues mapped to the phase,
|
|
6
|
+
# then updates labels and board column.
|
|
7
|
+
#
|
|
8
|
+
# Used by pp-core workflows (plan-phase, execute-phase) at lifecycle transitions.
|
|
9
|
+
# Non-blocking — failures are logged but never stop the pp-core workflow.
|
|
10
|
+
#
|
|
11
|
+
# Usage:
|
|
12
|
+
# bash sync-phase-status.sh <phase_number> <target_status> [--label-add X] [--label-remove Y]
|
|
13
|
+
#
|
|
14
|
+
# Examples:
|
|
15
|
+
# bash sync-phase-status.sh 4 "To Do"
|
|
16
|
+
# bash sync-phase-status.sh 4 "In Progress" --label-add in-progress --label-remove verification
|
|
17
|
+
# bash sync-phase-status.sh 4 "Verification" --label-add verification --label-remove in-progress
|
|
18
|
+
#
|
|
19
|
+
# Environment:
|
|
20
|
+
# GITHUB_ISSUES_JSON — path to mapping file (default: .planning/github-issues.json)
|
|
21
|
+
#
|
|
22
|
+
# Exit code: always 0 (non-blocking). Errors go to stderr.
|
|
23
|
+
|
|
24
|
+
set -uo pipefail
|
|
25
|
+
|
|
26
|
+
PHASE="$1"
|
|
27
|
+
TARGET_STATUS="$2"
|
|
28
|
+
shift 2
|
|
29
|
+
|
|
30
|
+
LABEL_ADD=""
|
|
31
|
+
LABEL_REMOVE=""
|
|
32
|
+
|
|
33
|
+
while [[ $# -gt 0 ]]; do
|
|
34
|
+
case "$1" in
|
|
35
|
+
--label-add) LABEL_ADD="$2"; shift 2 ;;
|
|
36
|
+
--label-remove) LABEL_REMOVE="$2"; shift 2 ;;
|
|
37
|
+
*) shift ;;
|
|
38
|
+
esac
|
|
39
|
+
done
|
|
40
|
+
|
|
41
|
+
MAPPING="${GITHUB_ISSUES_JSON:-.planning/github-issues.json}"
|
|
42
|
+
|
|
43
|
+
if [ ! -f "$MAPPING" ]; then
|
|
44
|
+
echo "sync-phase-status: no mapping file at $MAPPING — skipping board sync" >&2
|
|
45
|
+
exit 0
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
# Extract repo and board info
|
|
49
|
+
REPO=$(jq -r '.repo // empty' "$MAPPING" 2>/dev/null)
|
|
50
|
+
BOARD_NUM=$(jq -r '.board.number // empty' "$MAPPING" 2>/dev/null)
|
|
51
|
+
BOARD_OWNER=$(jq -r '.board.owner // empty' "$MAPPING" 2>/dev/null)
|
|
52
|
+
|
|
53
|
+
if [ -z "$REPO" ]; then
|
|
54
|
+
echo "sync-phase-status: no repo in mapping file — skipping" >&2
|
|
55
|
+
exit 0
|
|
56
|
+
fi
|
|
57
|
+
|
|
58
|
+
# Get all issue numbers for this phase (stories only — EPICs are derived)
|
|
59
|
+
ISSUE_NUMBERS=$(jq -r --arg p "$PHASE" '
|
|
60
|
+
[.issues | to_entries[]
|
|
61
|
+
| select(.value.phase == ($p | tonumber) and .value.type == "story")
|
|
62
|
+
| .value.number] | .[]
|
|
63
|
+
' "$MAPPING" 2>/dev/null)
|
|
64
|
+
|
|
65
|
+
if [ -z "$ISSUE_NUMBERS" ]; then
|
|
66
|
+
echo "sync-phase-status: no stories mapped to phase $PHASE — skipping" >&2
|
|
67
|
+
exit 0
|
|
68
|
+
fi
|
|
69
|
+
|
|
70
|
+
COUNT=$(echo "$ISSUE_NUMBERS" | wc -l | tr -d ' ')
|
|
71
|
+
echo "sync-phase-status: moving $COUNT stories for phase $PHASE → $TARGET_STATUS"
|
|
72
|
+
|
|
73
|
+
# Update labels on each issue
|
|
74
|
+
for NUM in $ISSUE_NUMBERS; do
|
|
75
|
+
LABEL_CMD="gh issue edit $NUM -R $REPO"
|
|
76
|
+
[ -n "$LABEL_ADD" ] && LABEL_CMD="$LABEL_CMD --add-label $LABEL_ADD"
|
|
77
|
+
[ -n "$LABEL_REMOVE" ] && LABEL_CMD="$LABEL_CMD --remove-label $LABEL_REMOVE"
|
|
78
|
+
|
|
79
|
+
if [ -n "$LABEL_ADD" ] || [ -n "$LABEL_REMOVE" ]; then
|
|
80
|
+
eval "$LABEL_CMD" 2>/dev/null || echo " warn: label update failed for #$NUM" >&2
|
|
81
|
+
fi
|
|
82
|
+
done
|
|
83
|
+
|
|
84
|
+
# Update board column (if board info available)
|
|
85
|
+
if [ -n "$BOARD_NUM" ] && [ -n "$BOARD_OWNER" ]; then
|
|
86
|
+
# Resolve board project ID and Status field
|
|
87
|
+
PROJECT_ID=$(gh project view "$BOARD_NUM" --owner "$BOARD_OWNER" --format json --jq '.id' 2>/dev/null)
|
|
88
|
+
|
|
89
|
+
if [ -z "$PROJECT_ID" ]; then
|
|
90
|
+
echo "sync-phase-status: could not resolve project board — skipping column update" >&2
|
|
91
|
+
exit 0
|
|
92
|
+
fi
|
|
93
|
+
|
|
94
|
+
STATUS_FIELD_ID=$(gh project field-list "$BOARD_NUM" --owner "$BOARD_OWNER" --format json \
|
|
95
|
+
--jq '.fields[] | select(.name=="Status") | .id' 2>/dev/null)
|
|
96
|
+
|
|
97
|
+
OPTION_ID=$(gh project field-list "$BOARD_NUM" --owner "$BOARD_OWNER" --format json \
|
|
98
|
+
--jq ".fields[] | select(.name==\"Status\") | .options[] | select(.name==\"$TARGET_STATUS\") | .id" 2>/dev/null)
|
|
99
|
+
|
|
100
|
+
if [ -z "$STATUS_FIELD_ID" ] || [ -z "$OPTION_ID" ]; then
|
|
101
|
+
echo "sync-phase-status: could not resolve Status field or option '$TARGET_STATUS' — skipping column update" >&2
|
|
102
|
+
exit 0
|
|
103
|
+
fi
|
|
104
|
+
|
|
105
|
+
# Get all board items and match by issue number (--limit 200 to avoid default 30 cap)
|
|
106
|
+
ITEMS_JSON=$(gh project item-list "$BOARD_NUM" --owner "$BOARD_OWNER" --format json --limit 200 2>/dev/null)
|
|
107
|
+
|
|
108
|
+
for NUM in $ISSUE_NUMBERS; do
|
|
109
|
+
ITEM_ID=$(echo "$ITEMS_JSON" | jq -r --argjson n "$NUM" '
|
|
110
|
+
.items[] | select(.content.number == $n) | .id
|
|
111
|
+
' 2>/dev/null)
|
|
112
|
+
|
|
113
|
+
if [ -n "$ITEM_ID" ] && [ "$ITEM_ID" != "null" ]; then
|
|
114
|
+
gh project item-edit \
|
|
115
|
+
--project-id "$PROJECT_ID" \
|
|
116
|
+
--id "$ITEM_ID" \
|
|
117
|
+
--field-id "$STATUS_FIELD_ID" \
|
|
118
|
+
--single-select-option-id "$OPTION_ID" 2>/dev/null \
|
|
119
|
+
&& echo " ✓ #$NUM → $TARGET_STATUS" \
|
|
120
|
+
|| echo " warn: board update failed for #$NUM" >&2
|
|
121
|
+
else
|
|
122
|
+
echo " warn: #$NUM not found on board — skipping column update" >&2
|
|
123
|
+
fi
|
|
124
|
+
done
|
|
125
|
+
fi
|
|
126
|
+
|
|
127
|
+
# Update status in the mapping file
|
|
128
|
+
jq --arg p "$PHASE" --arg s "$TARGET_STATUS" '
|
|
129
|
+
.issues |= with_entries(
|
|
130
|
+
if (.value.phase == ($p | tonumber) and .value.type == "story")
|
|
131
|
+
then .value.status = $s
|
|
132
|
+
else .
|
|
133
|
+
end
|
|
134
|
+
)
|
|
135
|
+
| .last_synced = (now | strftime("%Y-%m-%d"))
|
|
136
|
+
' "$MAPPING" > "${MAPPING}.tmp" && mv "${MAPPING}.tmp" "$MAPPING"
|
|
137
|
+
|
|
138
|
+
echo "sync-phase-status: done — $COUNT stories updated in mapping"
|
|
139
|
+
exit 0
|