@snipcodeit/mgw 0.2.2 → 0.3.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/README.md +55 -5
- package/commands/board/configure.md +205 -0
- package/commands/board/create.md +496 -0
- package/commands/board/show.md +221 -0
- package/commands/board/sync.md +417 -0
- package/commands/board/views.md +230 -0
- package/commands/board.md +23 -1543
- package/commands/milestone.md +5 -38
- package/commands/review.md +222 -42
- package/commands/run/execute.md +675 -0
- package/commands/run/pr-create.md +282 -0
- package/commands/run/triage.md +510 -0
- package/commands/run/worktree.md +54 -0
- package/commands/run.md +23 -1547
- package/commands/workflows/gsd.md +1 -13
- package/dist/bin/mgw.cjs +95 -6
- package/dist/{index-BiwU0uWA.cjs → index-s7v-ifd0.cjs} +669 -48
- package/dist/lib/index.cjs +185 -142
- package/package.json +5 -2
|
@@ -0,0 +1,496 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: board:create
|
|
3
|
+
description: Create the GitHub Projects v2 board and custom fields (idempotent)
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
<step name="subcommand_create">
|
|
7
|
+
**Execute 'create' subcommand:**
|
|
8
|
+
|
|
9
|
+
Only run if `$SUBCOMMAND = "create"`.
|
|
10
|
+
|
|
11
|
+
**Idempotency check:**
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
if [ "$SUBCOMMAND" = "create" ]; then
|
|
15
|
+
if [ "$BOARD_CONFIGURED" = "true" ]; then
|
|
16
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
17
|
+
echo " MGW ► BOARD ALREADY CONFIGURED"
|
|
18
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
19
|
+
echo ""
|
|
20
|
+
echo "Board: #${BOARD_NUMBER} — ${BOARD_URL}"
|
|
21
|
+
echo "Node ID: ${BOARD_NODE_ID}"
|
|
22
|
+
echo ""
|
|
23
|
+
echo "Custom fields:"
|
|
24
|
+
echo "$FIELDS_JSON" | python3 -c "
|
|
25
|
+
import json,sys
|
|
26
|
+
fields = json.load(sys.stdin)
|
|
27
|
+
for name, data in fields.items():
|
|
28
|
+
print(f\" {name}: {data.get('field_id', 'unknown')} ({data.get('type','?')})\")
|
|
29
|
+
" 2>/dev/null
|
|
30
|
+
echo ""
|
|
31
|
+
echo "To update field options: /mgw:board configure"
|
|
32
|
+
echo "To see board items: /mgw:board show"
|
|
33
|
+
exit 0
|
|
34
|
+
fi
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**Board discovery: check GitHub for an existing board before creating a new one:**
|
|
38
|
+
|
|
39
|
+
One lightweight GraphQL list call. Searches the first 20 user/org projects for a title
|
|
40
|
+
containing the project name. If found, registers it in project.json and exits — no fields
|
|
41
|
+
created, no board duplicated. Only runs when `BOARD_CONFIGURED = false`.
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
echo "Checking GitHub for existing boards..."
|
|
45
|
+
DISCOVERED=$(node -e "
|
|
46
|
+
const { findExistingBoard, getProjectFields } = require('./lib/github.cjs');
|
|
47
|
+
const board = findExistingBoard('${OWNER}', '${PROJECT_NAME}');
|
|
48
|
+
if (!board) { process.stdout.write(''); process.exit(0); }
|
|
49
|
+
const fields = getProjectFields('${OWNER}', board.number) || {};
|
|
50
|
+
console.log(JSON.stringify({ ...board, fields }));
|
|
51
|
+
" 2>/dev/null || echo "")
|
|
52
|
+
|
|
53
|
+
if [ -n "$DISCOVERED" ]; then
|
|
54
|
+
DISC_NUMBER=$(echo "$DISCOVERED" | python3 -c "import json,sys; print(json.load(sys.stdin)['number'])")
|
|
55
|
+
DISC_URL=$(echo "$DISCOVERED" | python3 -c "import json,sys; print(json.load(sys.stdin)['url'])")
|
|
56
|
+
DISC_NODE_ID=$(echo "$DISCOVERED" | python3 -c "import json,sys; print(json.load(sys.stdin)['nodeId'])")
|
|
57
|
+
DISC_TITLE=$(echo "$DISCOVERED" | python3 -c "import json,sys; print(json.load(sys.stdin)['title'])")
|
|
58
|
+
DISC_FIELDS=$(echo "$DISCOVERED" | python3 -c "import json,sys; print(json.dumps(json.load(sys.stdin).get('fields', {})))")
|
|
59
|
+
|
|
60
|
+
echo " Found existing board: #${DISC_NUMBER} \"${DISC_TITLE}\" — ${DISC_URL}"
|
|
61
|
+
|
|
62
|
+
python3 << PYEOF
|
|
63
|
+
import json
|
|
64
|
+
|
|
65
|
+
with open('${MGW_DIR}/project.json') as f:
|
|
66
|
+
project = json.load(f)
|
|
67
|
+
|
|
68
|
+
fields = json.loads('''${DISC_FIELDS}''') if '${DISC_FIELDS}' not in ('', '{}') else {}
|
|
69
|
+
|
|
70
|
+
project['project']['project_board'] = {
|
|
71
|
+
'number': int('${DISC_NUMBER}'),
|
|
72
|
+
'url': '${DISC_URL}',
|
|
73
|
+
'node_id': '${DISC_NODE_ID}',
|
|
74
|
+
'fields': fields
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
with open('${MGW_DIR}/project.json', 'w') as f:
|
|
78
|
+
json.dump(project, f, indent=2)
|
|
79
|
+
|
|
80
|
+
print('project.json updated')
|
|
81
|
+
PYEOF
|
|
82
|
+
|
|
83
|
+
echo ""
|
|
84
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
85
|
+
echo " MGW ► EXISTING BOARD REGISTERED"
|
|
86
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
87
|
+
echo ""
|
|
88
|
+
echo "Board: #${DISC_NUMBER} — ${DISC_URL}"
|
|
89
|
+
echo "Node ID: ${DISC_NODE_ID}"
|
|
90
|
+
echo ""
|
|
91
|
+
if [ "$DISC_FIELDS" != "{}" ] && [ -n "$DISC_FIELDS" ]; then
|
|
92
|
+
echo "Fields registered:"
|
|
93
|
+
echo "$DISC_FIELDS" | python3 -c "
|
|
94
|
+
import json,sys
|
|
95
|
+
fields = json.load(sys.stdin)
|
|
96
|
+
for name, data in fields.items():
|
|
97
|
+
ftype = data.get('type', '?')
|
|
98
|
+
print(f' {name}: {data.get(\"field_id\",\"?\")} ({ftype})')
|
|
99
|
+
" 2>/dev/null
|
|
100
|
+
else
|
|
101
|
+
echo " (no custom fields found — run /mgw:board configure to add them)"
|
|
102
|
+
fi
|
|
103
|
+
echo ""
|
|
104
|
+
echo "To see board items: /mgw:board show"
|
|
105
|
+
exit 0
|
|
106
|
+
fi
|
|
107
|
+
|
|
108
|
+
echo " No existing board found — creating new board..."
|
|
109
|
+
echo ""
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
**Get owner and repo node IDs (required for GraphQL mutations):**
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
OWNER_ID=$(gh api graphql -f query='
|
|
116
|
+
query($login: String!) {
|
|
117
|
+
user(login: $login) { id }
|
|
118
|
+
}
|
|
119
|
+
' -f login="$OWNER" --jq '.data.user.id' 2>/dev/null)
|
|
120
|
+
|
|
121
|
+
# Fall back to org if user lookup fails
|
|
122
|
+
if [ -z "$OWNER_ID" ]; then
|
|
123
|
+
OWNER_ID=$(gh api graphql -f query='
|
|
124
|
+
query($login: String!) {
|
|
125
|
+
organization(login: $login) { id }
|
|
126
|
+
}
|
|
127
|
+
' -f login="$OWNER" --jq '.data.organization.id' 2>/dev/null)
|
|
128
|
+
fi
|
|
129
|
+
|
|
130
|
+
if [ -z "$OWNER_ID" ]; then
|
|
131
|
+
echo "ERROR: Cannot resolve owner ID for '${OWNER}'. Check your GitHub token permissions."
|
|
132
|
+
exit 1
|
|
133
|
+
fi
|
|
134
|
+
|
|
135
|
+
REPO_NODE_ID=$(gh api graphql -f query='
|
|
136
|
+
query($owner: String!, $name: String!) {
|
|
137
|
+
repository(owner: $owner, name: $name) { id }
|
|
138
|
+
}
|
|
139
|
+
' -f owner="$OWNER" -f name="$REPO_NAME" --jq '.data.repository.id' 2>/dev/null)
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**Create the project board:**
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
BOARD_TITLE="${PROJECT_NAME} — MGW Pipeline Board"
|
|
146
|
+
echo "Creating GitHub Projects v2 board: '${BOARD_TITLE}'..."
|
|
147
|
+
|
|
148
|
+
CREATE_RESULT=$(gh api graphql -f query='
|
|
149
|
+
mutation($ownerId: ID!, $title: String!) {
|
|
150
|
+
createProjectV2(input: {
|
|
151
|
+
ownerId: $ownerId
|
|
152
|
+
title: $title
|
|
153
|
+
}) {
|
|
154
|
+
projectV2 {
|
|
155
|
+
id
|
|
156
|
+
number
|
|
157
|
+
url
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
' -f ownerId="$OWNER_ID" -f title="$BOARD_TITLE" 2>&1)
|
|
162
|
+
|
|
163
|
+
NEW_PROJECT_ID=$(echo "$CREATE_RESULT" | python3 -c "
|
|
164
|
+
import json,sys
|
|
165
|
+
d = json.load(sys.stdin)
|
|
166
|
+
print(d['data']['createProjectV2']['projectV2']['id'])
|
|
167
|
+
" 2>/dev/null)
|
|
168
|
+
|
|
169
|
+
NEW_PROJECT_NUMBER=$(echo "$CREATE_RESULT" | python3 -c "
|
|
170
|
+
import json,sys
|
|
171
|
+
d = json.load(sys.stdin)
|
|
172
|
+
print(d['data']['createProjectV2']['projectV2']['number'])
|
|
173
|
+
" 2>/dev/null)
|
|
174
|
+
|
|
175
|
+
NEW_PROJECT_URL=$(echo "$CREATE_RESULT" | python3 -c "
|
|
176
|
+
import json,sys
|
|
177
|
+
d = json.load(sys.stdin)
|
|
178
|
+
print(d['data']['createProjectV2']['projectV2']['url'])
|
|
179
|
+
" 2>/dev/null)
|
|
180
|
+
|
|
181
|
+
if [ -z "$NEW_PROJECT_ID" ]; then
|
|
182
|
+
echo "ERROR: Failed to create project board."
|
|
183
|
+
echo "GraphQL response: ${CREATE_RESULT}"
|
|
184
|
+
exit 1
|
|
185
|
+
fi
|
|
186
|
+
|
|
187
|
+
echo " Created board: #${NEW_PROJECT_NUMBER} — ${NEW_PROJECT_URL}"
|
|
188
|
+
echo " Board node ID: ${NEW_PROJECT_ID}"
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
**Create custom fields (Status, AI Agent State, Milestone, Phase, GSD Route):**
|
|
192
|
+
|
|
193
|
+
Field definitions follow docs/BOARD-SCHEMA.md from issue #71.
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
echo ""
|
|
197
|
+
echo "Creating custom fields..."
|
|
198
|
+
|
|
199
|
+
# Field 1: Status (SINGLE_SELECT — maps to pipeline_stage)
|
|
200
|
+
STATUS_RESULT=$(gh api graphql -f query='
|
|
201
|
+
mutation($projectId: ID!) {
|
|
202
|
+
createProjectV2Field(input: {
|
|
203
|
+
projectId: $projectId
|
|
204
|
+
dataType: SINGLE_SELECT
|
|
205
|
+
name: "Status"
|
|
206
|
+
singleSelectOptions: [
|
|
207
|
+
{ name: "New", color: GRAY, description: "Issue created, not yet triaged" }
|
|
208
|
+
{ name: "Triaged", color: BLUE, description: "Triage complete, ready for execution" }
|
|
209
|
+
{ name: "Needs Info", color: YELLOW, description: "Blocked at triage gate" }
|
|
210
|
+
{ name: "Needs Security Review", color: RED, description: "High security risk flagged" }
|
|
211
|
+
{ name: "Discussing", color: PURPLE, description: "Awaiting stakeholder scope approval" }
|
|
212
|
+
{ name: "Approved", color: GREEN, description: "Cleared for execution" }
|
|
213
|
+
{ name: "Planning", color: BLUE, description: "GSD planner agent active" }
|
|
214
|
+
{ name: "Executing", color: ORANGE, description: "GSD executor agent active" }
|
|
215
|
+
{ name: "Verifying", color: BLUE, description: "GSD verifier agent active" }
|
|
216
|
+
{ name: "PR Created", color: GREEN, description: "PR open, awaiting review" }
|
|
217
|
+
{ name: "Done", color: GREEN, description: "PR merged, issue closed" }
|
|
218
|
+
{ name: "Failed", color: RED, description: "Unrecoverable pipeline error" }
|
|
219
|
+
{ name: "Blocked", color: RED, description: "Blocking comment detected" }
|
|
220
|
+
]
|
|
221
|
+
}) {
|
|
222
|
+
projectV2Field {
|
|
223
|
+
... on ProjectV2SingleSelectField {
|
|
224
|
+
id
|
|
225
|
+
name
|
|
226
|
+
options { id name }
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
' -f projectId="$NEW_PROJECT_ID" 2>&1)
|
|
232
|
+
|
|
233
|
+
STATUS_FIELD_ID=$(echo "$STATUS_RESULT" | python3 -c "
|
|
234
|
+
import json,sys
|
|
235
|
+
d = json.load(sys.stdin)
|
|
236
|
+
print(d['data']['createProjectV2Field']['projectV2Field']['id'])
|
|
237
|
+
" 2>/dev/null)
|
|
238
|
+
|
|
239
|
+
# Build option ID map from result
|
|
240
|
+
STATUS_OPTIONS=$(echo "$STATUS_RESULT" | python3 -c "
|
|
241
|
+
import json,sys
|
|
242
|
+
d = json.load(sys.stdin)
|
|
243
|
+
options = d['data']['createProjectV2Field']['projectV2Field']['options']
|
|
244
|
+
# Map lowercase pipeline_stage keys to option IDs
|
|
245
|
+
stage_map = {
|
|
246
|
+
'new': 'New', 'triaged': 'Triaged', 'needs-info': 'Needs Info',
|
|
247
|
+
'needs-security-review': 'Needs Security Review', 'discussing': 'Discussing',
|
|
248
|
+
'approved': 'Approved', 'planning': 'Planning', 'executing': 'Executing',
|
|
249
|
+
'verifying': 'Verifying', 'pr-created': 'PR Created', 'done': 'Done',
|
|
250
|
+
'failed': 'Failed', 'blocked': 'Blocked'
|
|
251
|
+
}
|
|
252
|
+
name_to_id = {o['name']: o['id'] for o in options}
|
|
253
|
+
result = {stage: name_to_id.get(display, '') for stage, display in stage_map.items()}
|
|
254
|
+
print(json.dumps(result))
|
|
255
|
+
" 2>/dev/null || echo "{}")
|
|
256
|
+
|
|
257
|
+
if [ -n "$STATUS_FIELD_ID" ]; then
|
|
258
|
+
echo " Status field created: ${STATUS_FIELD_ID}"
|
|
259
|
+
else
|
|
260
|
+
echo " WARNING: Status field creation failed: ${STATUS_RESULT}"
|
|
261
|
+
fi
|
|
262
|
+
|
|
263
|
+
# Field 2: AI Agent State (TEXT)
|
|
264
|
+
AI_STATE_RESULT=$(gh api graphql -f query='
|
|
265
|
+
mutation($projectId: ID!) {
|
|
266
|
+
createProjectV2Field(input: {
|
|
267
|
+
projectId: $projectId
|
|
268
|
+
dataType: TEXT
|
|
269
|
+
name: "AI Agent State"
|
|
270
|
+
}) {
|
|
271
|
+
projectV2Field {
|
|
272
|
+
... on ProjectV2Field {
|
|
273
|
+
id
|
|
274
|
+
name
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
' -f projectId="$NEW_PROJECT_ID" 2>&1)
|
|
280
|
+
|
|
281
|
+
AI_STATE_FIELD_ID=$(echo "$AI_STATE_RESULT" | python3 -c "
|
|
282
|
+
import json,sys
|
|
283
|
+
d = json.load(sys.stdin)
|
|
284
|
+
print(d['data']['createProjectV2Field']['projectV2Field']['id'])
|
|
285
|
+
" 2>/dev/null)
|
|
286
|
+
|
|
287
|
+
if [ -n "$AI_STATE_FIELD_ID" ]; then
|
|
288
|
+
echo " AI Agent State field created: ${AI_STATE_FIELD_ID}"
|
|
289
|
+
else
|
|
290
|
+
echo " WARNING: AI Agent State field creation failed"
|
|
291
|
+
fi
|
|
292
|
+
|
|
293
|
+
# Field 3: Milestone (TEXT)
|
|
294
|
+
MILESTONE_FIELD_RESULT=$(gh api graphql -f query='
|
|
295
|
+
mutation($projectId: ID!) {
|
|
296
|
+
createProjectV2Field(input: {
|
|
297
|
+
projectId: $projectId
|
|
298
|
+
dataType: TEXT
|
|
299
|
+
name: "Milestone"
|
|
300
|
+
}) {
|
|
301
|
+
projectV2Field {
|
|
302
|
+
... on ProjectV2Field {
|
|
303
|
+
id
|
|
304
|
+
name
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
' -f projectId="$NEW_PROJECT_ID" 2>&1)
|
|
310
|
+
|
|
311
|
+
MILESTONE_FIELD_ID=$(echo "$MILESTONE_FIELD_RESULT" | python3 -c "
|
|
312
|
+
import json,sys
|
|
313
|
+
d = json.load(sys.stdin)
|
|
314
|
+
print(d['data']['createProjectV2Field']['projectV2Field']['id'])
|
|
315
|
+
" 2>/dev/null)
|
|
316
|
+
|
|
317
|
+
if [ -n "$MILESTONE_FIELD_ID" ]; then
|
|
318
|
+
echo " Milestone field created: ${MILESTONE_FIELD_ID}"
|
|
319
|
+
else
|
|
320
|
+
echo " WARNING: Milestone field creation failed"
|
|
321
|
+
fi
|
|
322
|
+
|
|
323
|
+
# Field 4: Phase (TEXT)
|
|
324
|
+
PHASE_FIELD_RESULT=$(gh api graphql -f query='
|
|
325
|
+
mutation($projectId: ID!) {
|
|
326
|
+
createProjectV2Field(input: {
|
|
327
|
+
projectId: $projectId
|
|
328
|
+
dataType: TEXT
|
|
329
|
+
name: "Phase"
|
|
330
|
+
}) {
|
|
331
|
+
projectV2Field {
|
|
332
|
+
... on ProjectV2Field {
|
|
333
|
+
id
|
|
334
|
+
name
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
' -f projectId="$NEW_PROJECT_ID" 2>&1)
|
|
340
|
+
|
|
341
|
+
PHASE_FIELD_ID=$(echo "$PHASE_FIELD_RESULT" | python3 -c "
|
|
342
|
+
import json,sys
|
|
343
|
+
d = json.load(sys.stdin)
|
|
344
|
+
print(d['data']['createProjectV2Field']['projectV2Field']['id'])
|
|
345
|
+
" 2>/dev/null)
|
|
346
|
+
|
|
347
|
+
if [ -n "$PHASE_FIELD_ID" ]; then
|
|
348
|
+
echo " Phase field created: ${PHASE_FIELD_ID}"
|
|
349
|
+
else
|
|
350
|
+
echo " WARNING: Phase field creation failed"
|
|
351
|
+
fi
|
|
352
|
+
|
|
353
|
+
# Field 5: GSD Route (SINGLE_SELECT)
|
|
354
|
+
GSD_ROUTE_RESULT=$(gh api graphql -f query='
|
|
355
|
+
mutation($projectId: ID!) {
|
|
356
|
+
createProjectV2Field(input: {
|
|
357
|
+
projectId: $projectId
|
|
358
|
+
dataType: SINGLE_SELECT
|
|
359
|
+
name: "GSD Route"
|
|
360
|
+
singleSelectOptions: [
|
|
361
|
+
{ name: "quick", color: BLUE, description: "Small/atomic task, direct execution" }
|
|
362
|
+
{ name: "quick --full", color: BLUE, description: "Small task with plan-checker and verifier" }
|
|
363
|
+
{ name: "plan-phase", color: PURPLE, description: "Medium task with phase planning" }
|
|
364
|
+
{ name: "new-milestone", color: ORANGE, description: "Large task with full milestone lifecycle" }
|
|
365
|
+
]
|
|
366
|
+
}) {
|
|
367
|
+
projectV2Field {
|
|
368
|
+
... on ProjectV2SingleSelectField {
|
|
369
|
+
id
|
|
370
|
+
name
|
|
371
|
+
options { id name }
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
' -f projectId="$NEW_PROJECT_ID" 2>&1)
|
|
377
|
+
|
|
378
|
+
GSD_ROUTE_FIELD_ID=$(echo "$GSD_ROUTE_RESULT" | python3 -c "
|
|
379
|
+
import json,sys
|
|
380
|
+
d = json.load(sys.stdin)
|
|
381
|
+
print(d['data']['createProjectV2Field']['projectV2Field']['id'])
|
|
382
|
+
" 2>/dev/null)
|
|
383
|
+
|
|
384
|
+
GSD_ROUTE_OPTIONS=$(echo "$GSD_ROUTE_RESULT" | python3 -c "
|
|
385
|
+
import json,sys
|
|
386
|
+
d = json.load(sys.stdin)
|
|
387
|
+
options = d['data']['createProjectV2Field']['projectV2Field']['options']
|
|
388
|
+
route_map = {
|
|
389
|
+
'gsd:quick': 'quick', 'gsd:quick --full': 'quick --full',
|
|
390
|
+
'gsd:plan-phase': 'plan-phase', 'gsd:new-milestone': 'new-milestone'
|
|
391
|
+
}
|
|
392
|
+
name_to_id = {o['name']: o['id'] for o in options}
|
|
393
|
+
result = {route: name_to_id.get(display, '') for route, display in route_map.items()}
|
|
394
|
+
print(json.dumps(result))
|
|
395
|
+
" 2>/dev/null || echo "{}")
|
|
396
|
+
|
|
397
|
+
if [ -n "$GSD_ROUTE_FIELD_ID" ]; then
|
|
398
|
+
echo " GSD Route field created: ${GSD_ROUTE_FIELD_ID}"
|
|
399
|
+
else
|
|
400
|
+
echo " WARNING: GSD Route field creation failed"
|
|
401
|
+
fi
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
**Update project.json with board metadata:**
|
|
405
|
+
|
|
406
|
+
```bash
|
|
407
|
+
echo ""
|
|
408
|
+
echo "Updating project.json with board metadata..."
|
|
409
|
+
|
|
410
|
+
python3 << PYEOF
|
|
411
|
+
import json
|
|
412
|
+
|
|
413
|
+
with open('${MGW_DIR}/project.json') as f:
|
|
414
|
+
project = json.load(f)
|
|
415
|
+
|
|
416
|
+
# Build field schema
|
|
417
|
+
status_options = json.loads('''${STATUS_OPTIONS}''') if '${STATUS_OPTIONS}' != '{}' else {}
|
|
418
|
+
gsd_route_options = json.loads('''${GSD_ROUTE_OPTIONS}''') if '${GSD_ROUTE_OPTIONS}' != '{}' else {}
|
|
419
|
+
|
|
420
|
+
fields = {}
|
|
421
|
+
|
|
422
|
+
if '${STATUS_FIELD_ID}':
|
|
423
|
+
fields['status'] = {
|
|
424
|
+
'field_id': '${STATUS_FIELD_ID}',
|
|
425
|
+
'field_name': 'Status',
|
|
426
|
+
'type': 'SINGLE_SELECT',
|
|
427
|
+
'options': status_options
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
if '${AI_STATE_FIELD_ID}':
|
|
431
|
+
fields['ai_agent_state'] = {
|
|
432
|
+
'field_id': '${AI_STATE_FIELD_ID}',
|
|
433
|
+
'field_name': 'AI Agent State',
|
|
434
|
+
'type': 'TEXT'
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
if '${MILESTONE_FIELD_ID}':
|
|
438
|
+
fields['milestone'] = {
|
|
439
|
+
'field_id': '${MILESTONE_FIELD_ID}',
|
|
440
|
+
'field_name': 'Milestone',
|
|
441
|
+
'type': 'TEXT'
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
if '${PHASE_FIELD_ID}':
|
|
445
|
+
fields['phase'] = {
|
|
446
|
+
'field_id': '${PHASE_FIELD_ID}',
|
|
447
|
+
'field_name': 'Phase',
|
|
448
|
+
'type': 'TEXT'
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
if '${GSD_ROUTE_FIELD_ID}':
|
|
452
|
+
fields['gsd_route'] = {
|
|
453
|
+
'field_id': '${GSD_ROUTE_FIELD_ID}',
|
|
454
|
+
'field_name': 'GSD Route',
|
|
455
|
+
'type': 'SINGLE_SELECT',
|
|
456
|
+
'options': gsd_route_options
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
# Update project_board section
|
|
460
|
+
project['project']['project_board'] = {
|
|
461
|
+
'number': int('${NEW_PROJECT_NUMBER}') if '${NEW_PROJECT_NUMBER}'.isdigit() else None,
|
|
462
|
+
'url': '${NEW_PROJECT_URL}',
|
|
463
|
+
'node_id': '${NEW_PROJECT_ID}',
|
|
464
|
+
'fields': fields
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
with open('${MGW_DIR}/project.json', 'w') as f:
|
|
468
|
+
json.dump(project, f, indent=2)
|
|
469
|
+
|
|
470
|
+
print('project.json updated')
|
|
471
|
+
PYEOF
|
|
472
|
+
|
|
473
|
+
echo ""
|
|
474
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
475
|
+
echo " MGW ► BOARD CREATED"
|
|
476
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
477
|
+
echo ""
|
|
478
|
+
echo "Board: #${NEW_PROJECT_NUMBER} — ${NEW_PROJECT_URL}"
|
|
479
|
+
echo "Node ID: ${NEW_PROJECT_ID}"
|
|
480
|
+
echo ""
|
|
481
|
+
echo "Custom fields created:"
|
|
482
|
+
echo " status ${STATUS_FIELD_ID:-FAILED} (SINGLE_SELECT, 13 options)"
|
|
483
|
+
echo " ai_agent_state ${AI_STATE_FIELD_ID:-FAILED} (TEXT)"
|
|
484
|
+
echo " milestone ${MILESTONE_FIELD_ID:-FAILED} (TEXT)"
|
|
485
|
+
echo " phase ${PHASE_FIELD_ID:-FAILED} (TEXT)"
|
|
486
|
+
echo " gsd_route ${GSD_ROUTE_FIELD_ID:-FAILED} (SINGLE_SELECT, 4 options)"
|
|
487
|
+
echo ""
|
|
488
|
+
echo "Field IDs stored in .mgw/project.json"
|
|
489
|
+
echo ""
|
|
490
|
+
echo "Next:"
|
|
491
|
+
echo " /mgw:board show Display board state"
|
|
492
|
+
echo " /mgw:run 73 Sync issues onto board items (#73)"
|
|
493
|
+
|
|
494
|
+
fi # end create subcommand
|
|
495
|
+
```
|
|
496
|
+
</step>
|