create-agentic-pdlc 3.0.0 → 3.1.1
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/.coderabbit.yaml +7 -1
- package/.github/workflows/add-to-board.yml +55 -7
- package/.github/workflows/agent-trigger.yml +57 -25
- package/.github/workflows/board-reconciliation.yml +176 -0
- package/.github/workflows/pdlc-health-check.yml +81 -81
- package/.github/workflows/project-automation.yml +256 -209
- package/adapters/claude-code/skill.md +5 -5
- package/bin/cli.js +112 -19
- package/docs/superpowers/plans/2026-06-05-archive-card-on-issue-close.md +105 -0
- package/docs/superpowers/plans/2026-06-05-project-id-actions-variable.md +336 -0
- package/docs/superpowers/specs/2026-06-05-project-id-actions-variable-design.md +114 -0
- package/package.json +1 -1
- package/scripts/derive-column.js +20 -0
- package/templates/.github/workflows/add-to-board.yml +2 -2
- package/templates/.github/workflows/agent-trigger.yml +2 -2
- package/templates/.github/workflows/pdlc-health-check.yml +2 -2
- package/templates/.github/workflows/project-automation.yml +47 -8
- package/tests/cli.test.js +86 -0
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
# PROJECT_ID as Actions Variable Implementation Plan
|
|
2
|
+
|
|
3
|
+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
4
|
+
|
|
5
|
+
**Goal:** Replace hardcoded `PROJECT_ID: "PVT_xxx"` in installed YAML files with `${{ vars.PROJECT_ID }}`, and have the installer set the value via the GitHub Actions Variables REST API.
|
|
6
|
+
|
|
7
|
+
**Architecture:** Three workflow templates swap their `PROJECT_ID` env value and guard condition. `bin/cli.js` gains a `setActionsVariable(repo, name, value, execFn)` helper (dependency-injected `execFn` for testability) called after board creation in both the `runFullSetup` and `runUpgradeToAgentic` flows.
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** Node.js 22, `node:test` + `node:assert/strict`, `gh` CLI (`execFileSync`), GitHub Actions Variables REST API (`PATCH`/`POST /repos/{owner}/{repo}/actions/variables`).
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## File Map
|
|
14
|
+
|
|
15
|
+
| Action | Path | Responsibility |
|
|
16
|
+
|---|---|---|
|
|
17
|
+
| Modify | `templates/.github/workflows/project-automation.yml` | Remove `{{PROJECT_ID}}` placeholder, source from `vars` |
|
|
18
|
+
| Modify | `templates/.github/workflows/add-to-board.yml` | Same |
|
|
19
|
+
| Modify | `templates/.github/workflows/agent-trigger.yml` | Same |
|
|
20
|
+
| Modify | `bin/cli.js` | Add helper, wire call sites, export helper |
|
|
21
|
+
| Modify | `tests/cli.test.js` | Tests for `setActionsVariable` logic |
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
### Task 1: Update workflow templates
|
|
26
|
+
|
|
27
|
+
**Files:**
|
|
28
|
+
- Modify: `templates/.github/workflows/project-automation.yml`
|
|
29
|
+
- Modify: `templates/.github/workflows/add-to-board.yml`
|
|
30
|
+
- Modify: `templates/.github/workflows/agent-trigger.yml`
|
|
31
|
+
|
|
32
|
+
No tests for template content — correctness is verified by the installer integration.
|
|
33
|
+
|
|
34
|
+
- [ ] **Step 1: Replace `PROJECT_ID` env value in all three templates**
|
|
35
|
+
|
|
36
|
+
In each of the three files, find every line matching:
|
|
37
|
+
```yaml
|
|
38
|
+
PROJECT_ID: "{{PROJECT_ID}}"
|
|
39
|
+
```
|
|
40
|
+
Replace with:
|
|
41
|
+
```yaml
|
|
42
|
+
PROJECT_ID: ${{ vars.PROJECT_ID }}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
`project-automation.yml` line 12, `add-to-board.yml` line 8, `agent-trigger.yml` line 21.
|
|
46
|
+
|
|
47
|
+
- [ ] **Step 2: Replace guard conditions in all three templates**
|
|
48
|
+
|
|
49
|
+
In each of the three files, find every line matching:
|
|
50
|
+
```yaml
|
|
51
|
+
env.PROJECT_ID != '{{PROJECT_ID}}'
|
|
52
|
+
```
|
|
53
|
+
Replace with:
|
|
54
|
+
```yaml
|
|
55
|
+
env.PROJECT_ID != ''
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Do a full-file search in each — there are multiple occurrences per file (every job that conditionally runs).
|
|
59
|
+
|
|
60
|
+
- [ ] **Step 3: Verify no `{{PROJECT_ID}}` remains in workflow files**
|
|
61
|
+
|
|
62
|
+
Run:
|
|
63
|
+
```bash
|
|
64
|
+
grep -rn "{{PROJECT_ID}}" templates/.github/workflows/
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Expected: no output. If any lines appear, fix them before proceeding.
|
|
68
|
+
|
|
69
|
+
- [ ] **Step 4: Commit**
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
git add templates/.github/workflows/project-automation.yml \
|
|
73
|
+
templates/.github/workflows/add-to-board.yml \
|
|
74
|
+
templates/.github/workflows/agent-trigger.yml
|
|
75
|
+
git commit -m "feat(templates): source PROJECT_ID from vars instead of hardcoded placeholder"
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
### Task 2: Remove `{{PROJECT_ID}}` YAML substitution from `scaffoldFullTemplates`
|
|
81
|
+
|
|
82
|
+
**Files:**
|
|
83
|
+
- Modify: `bin/cli.js:345`
|
|
84
|
+
|
|
85
|
+
- [ ] **Step 1: Delete the single PROJECT_ID substitution line**
|
|
86
|
+
|
|
87
|
+
In `bin/cli.js`, find and remove exactly this line (currently line 345):
|
|
88
|
+
```javascript
|
|
89
|
+
if (projectId) wfContent = wfContent.replace(/\{\{PROJECT_ID\}\}/g, () => projectId);
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
The block around it (lines 342–356) substitutes `STATUS_FIELD_ID` and all `ID_*` column options into `project-automation.yml`. Keep all those lines. Only the `PROJECT_ID` line is removed.
|
|
93
|
+
|
|
94
|
+
- [ ] **Step 2: Verify other substitutions are intact**
|
|
95
|
+
|
|
96
|
+
Run:
|
|
97
|
+
```bash
|
|
98
|
+
grep -n "wfContent.replace" bin/cli.js
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Expected output must include lines for `STATUS_FIELD_ID`, `ID_IDEA`, `ID_BRAINSTORMING`, `ID_DETAILING`, `ID_APPROVAL`, `ID_DEVELOPMENT`, `ID_TESTING`, `ID_CODE_REVIEW_PR`, `ID_PRODUCTION`. Must NOT include `PROJECT_ID`.
|
|
102
|
+
|
|
103
|
+
- [ ] **Step 3: Commit**
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
git add bin/cli.js
|
|
107
|
+
git commit -m "fix(cli): remove PROJECT_ID hardcoding from scaffoldFullTemplates"
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
### Task 3: Add `setActionsVariable` helper (TDD)
|
|
113
|
+
|
|
114
|
+
**Files:**
|
|
115
|
+
- Modify: `bin/cli.js` (add function before `scaffoldFullTemplates`, ~line 283)
|
|
116
|
+
- Modify: `tests/cli.test.js` (add describe block)
|
|
117
|
+
- Modify: `bin/cli.js:793` (add to `module.exports`)
|
|
118
|
+
|
|
119
|
+
- [ ] **Step 1: Write failing tests**
|
|
120
|
+
|
|
121
|
+
Add to `tests/cli.test.js`:
|
|
122
|
+
|
|
123
|
+
```javascript
|
|
124
|
+
const { describe, it, mock } = require('node:test');
|
|
125
|
+
|
|
126
|
+
// ... existing imports and tests above ...
|
|
127
|
+
|
|
128
|
+
describe('setActionsVariable', () => {
|
|
129
|
+
it('calls PATCH first', () => {
|
|
130
|
+
const calls = [];
|
|
131
|
+
const execFn = (cmd, args) => { calls.push(args); };
|
|
132
|
+
const { setActionsVariable } = require('../bin/cli.js');
|
|
133
|
+
setActionsVariable('owner/repo', 'PROJECT_ID', 'PVT_abc', execFn);
|
|
134
|
+
assert.equal(calls.length, 1);
|
|
135
|
+
assert.ok(calls[0].includes('--method'));
|
|
136
|
+
assert.ok(calls[0].includes('PATCH'));
|
|
137
|
+
assert.ok(calls[0].some(a => a.includes('PROJECT_ID')));
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('falls back to POST on 404', () => {
|
|
141
|
+
const calls = [];
|
|
142
|
+
let callCount = 0;
|
|
143
|
+
const execFn = (cmd, args) => {
|
|
144
|
+
calls.push([...args]);
|
|
145
|
+
callCount++;
|
|
146
|
+
if (callCount === 1) {
|
|
147
|
+
const err = new Error('Not Found');
|
|
148
|
+
err.stderr = Buffer.from('Not Found');
|
|
149
|
+
throw err;
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
const { setActionsVariable } = require('../bin/cli.js');
|
|
153
|
+
setActionsVariable('owner/repo', 'PROJECT_ID', 'PVT_abc', execFn);
|
|
154
|
+
assert.equal(calls.length, 2);
|
|
155
|
+
assert.ok(calls[0].includes('PATCH'));
|
|
156
|
+
assert.ok(calls[1].includes('POST'));
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('throws on 403', () => {
|
|
160
|
+
const execFn = () => {
|
|
161
|
+
const err = new Error('Forbidden');
|
|
162
|
+
err.stderr = Buffer.from('Forbidden');
|
|
163
|
+
throw err;
|
|
164
|
+
};
|
|
165
|
+
const { setActionsVariable } = require('../bin/cli.js');
|
|
166
|
+
assert.throws(
|
|
167
|
+
() => setActionsVariable('owner/repo', 'PROJECT_ID', 'PVT_abc', execFn),
|
|
168
|
+
/Forbidden/
|
|
169
|
+
);
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
- [ ] **Step 2: Run tests to verify they fail**
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
npm test
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Expected: 3 new test failures — `setActionsVariable` is not exported yet.
|
|
181
|
+
|
|
182
|
+
- [ ] **Step 3: Add `setActionsVariable` to `bin/cli.js`**
|
|
183
|
+
|
|
184
|
+
Insert this function before `scaffoldFullTemplates` (~line 283):
|
|
185
|
+
|
|
186
|
+
```javascript
|
|
187
|
+
function setActionsVariable(repo, name, value, execFn = execFileSync) {
|
|
188
|
+
try {
|
|
189
|
+
execFn('gh', [
|
|
190
|
+
'api', `repos/${repo}/actions/variables/${name}`,
|
|
191
|
+
'--method', 'PATCH',
|
|
192
|
+
'-f', `name=${name}`,
|
|
193
|
+
'-f', `value=${value}`
|
|
194
|
+
], { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
195
|
+
} catch (err) {
|
|
196
|
+
const msg = (err.stderr?.toString() || '') + (err.message || '');
|
|
197
|
+
if (msg.includes('404') || msg.includes('Not Found')) {
|
|
198
|
+
execFn('gh', [
|
|
199
|
+
'api', `repos/${repo}/actions/variables`,
|
|
200
|
+
'--method', 'POST',
|
|
201
|
+
'-f', `name=${name}`,
|
|
202
|
+
'-f', `value=${value}`
|
|
203
|
+
], { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
204
|
+
} else {
|
|
205
|
+
throw err;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
- [ ] **Step 4: Export the function**
|
|
212
|
+
|
|
213
|
+
In `bin/cli.js` at line 793, update:
|
|
214
|
+
```javascript
|
|
215
|
+
if (typeof module !== 'undefined') module.exports = { resolveMode };
|
|
216
|
+
```
|
|
217
|
+
to:
|
|
218
|
+
```javascript
|
|
219
|
+
if (typeof module !== 'undefined') module.exports = { resolveMode, setActionsVariable };
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
- [ ] **Step 5: Run tests to verify they pass**
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
npm test
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Expected: all tests pass including the 3 new `setActionsVariable` tests.
|
|
229
|
+
|
|
230
|
+
- [ ] **Step 6: Commit**
|
|
231
|
+
|
|
232
|
+
```bash
|
|
233
|
+
git add bin/cli.js tests/cli.test.js
|
|
234
|
+
git commit -m "feat(cli): add setActionsVariable helper with PATCH→POST fallback"
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
### Task 4: Wire `setActionsVariable` into the create flow (`runFullSetup`)
|
|
240
|
+
|
|
241
|
+
**Files:**
|
|
242
|
+
- Modify: `bin/cli.js` (~line 554)
|
|
243
|
+
|
|
244
|
+
- [ ] **Step 1: Add the call site after the PROJECT_PAT block**
|
|
245
|
+
|
|
246
|
+
In `bin/cli.js`, locate the end of the `PROJECT_PAT` block in `runFullSetup` (the `} else if (projectId && isOrg)` line at ~554). Add after it:
|
|
247
|
+
|
|
248
|
+
```javascript
|
|
249
|
+
// Set PROJECT_ID as GitHub Actions Variable
|
|
250
|
+
if (projectId) {
|
|
251
|
+
try {
|
|
252
|
+
setActionsVariable(repo, 'PROJECT_ID', projectId);
|
|
253
|
+
console.log(`${green}✅ vars.PROJECT_ID set as Actions Variable.${reset}`);
|
|
254
|
+
} catch (_) {
|
|
255
|
+
console.log(`${yellow}⚠️ Could not set vars.PROJECT_ID — token may lack variables:write scope.\n Set manually: repo Settings → Secrets and variables → Variables → PROJECT_ID = ${projectId}${reset}`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
Insert this block between line 554 (`} else if (projectId && isOrg) { ... }`) and line 556 (`await setBranchProtection(...)`).
|
|
261
|
+
|
|
262
|
+
- [ ] **Step 2: Verify placement**
|
|
263
|
+
|
|
264
|
+
```bash
|
|
265
|
+
grep -n "vars.PROJECT_ID\|setBranchProtection\|isOrg\)" bin/cli.js | head -20
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
The `vars.PROJECT_ID` console log should appear between the `isOrg` block and `setBranchProtection`.
|
|
269
|
+
|
|
270
|
+
- [ ] **Step 3: Commit**
|
|
271
|
+
|
|
272
|
+
```bash
|
|
273
|
+
git add bin/cli.js
|
|
274
|
+
git commit -m "feat(cli): set vars.PROJECT_ID as Actions Variable in create flow"
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
---
|
|
278
|
+
|
|
279
|
+
### Task 5: Wire `setActionsVariable` into the upgrade flow (`runUpgradeToAgentic`)
|
|
280
|
+
|
|
281
|
+
**Files:**
|
|
282
|
+
- Modify: `bin/cli.js` (~line 1006)
|
|
283
|
+
|
|
284
|
+
- [ ] **Step 1: Add the call site after the PROJECT_PAT block in the upgrade flow**
|
|
285
|
+
|
|
286
|
+
In `bin/cli.js`, locate the end of the `PROJECT_PAT` block in `runUpgradeToAgentic` (the closing `}` of `if (projectId && !isOrg)` at ~line 1006). Add after it:
|
|
287
|
+
|
|
288
|
+
```javascript
|
|
289
|
+
// Set PROJECT_ID as GitHub Actions Variable
|
|
290
|
+
if (projectId) {
|
|
291
|
+
try {
|
|
292
|
+
setActionsVariable(repo, 'PROJECT_ID', projectId);
|
|
293
|
+
console.log(`${green}✅ vars.PROJECT_ID set as Actions Variable.${reset}`);
|
|
294
|
+
} catch (_) {
|
|
295
|
+
console.log(`${yellow}⚠️ Could not set vars.PROJECT_ID — token may lack variables:write scope.\n Set manually: repo Settings → Secrets and variables → Variables → PROJECT_ID = ${projectId}${reset}`);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
Insert between line ~1006 (`}` closing the `PROJECT_PAT` block) and line ~1008 (`console.log scaffolding`).
|
|
301
|
+
|
|
302
|
+
- [ ] **Step 2: Verify no `{{PROJECT_ID}}` placeholder survives install**
|
|
303
|
+
|
|
304
|
+
```bash
|
|
305
|
+
grep -rn "{{PROJECT_ID}}" templates/
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
Expected: only `templates/full/docs/pdlc.md` (the documentation file). No workflow YAML files.
|
|
309
|
+
|
|
310
|
+
- [ ] **Step 3: Run full test suite**
|
|
311
|
+
|
|
312
|
+
```bash
|
|
313
|
+
npm test
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
Expected: all tests pass.
|
|
317
|
+
|
|
318
|
+
- [ ] **Step 4: Commit**
|
|
319
|
+
|
|
320
|
+
```bash
|
|
321
|
+
git add bin/cli.js
|
|
322
|
+
git commit -m "feat(cli): set vars.PROJECT_ID as Actions Variable in upgrade flow"
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
---
|
|
326
|
+
|
|
327
|
+
## Self-Review Checklist
|
|
328
|
+
|
|
329
|
+
- Acceptance Criteria 1 (new install sets `vars.PROJECT_ID`): Task 4 ✓
|
|
330
|
+
- Acceptance Criteria 2 (`resolve-ids` works without YAML modification): Task 1 ✓
|
|
331
|
+
- Acceptance Criteria 3 (`--update` sets variable): Task 5 ✓
|
|
332
|
+
- Acceptance Criteria 4 (403 warning, non-fatal): Tasks 3, 4, 5 ✓
|
|
333
|
+
- Edge case — PATCH first, POST on 404: Task 3 ✓
|
|
334
|
+
- Edge case — `vars.PROJECT_ID` unset resolves to `''`, guard works: Task 1 ✓
|
|
335
|
+
- No `{{PROJECT_ID}}` in any YAML after install: Tasks 1 + 2 ✓
|
|
336
|
+
- All other ID substitutions preserved: Task 2 Step 2 ✓
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# Design: Set PROJECT_ID as Actions Variable During Install
|
|
2
|
+
|
|
3
|
+
**Issue:** #179
|
|
4
|
+
**Date:** 2026-06-05
|
|
5
|
+
**Status:** Approved
|
|
6
|
+
|
|
7
|
+
## Problem
|
|
8
|
+
|
|
9
|
+
The `create-agentic-pdlc` installer embeds the GitHub Project ID (`PVT_xxx`) directly into workflow YAML files by replacing `{{PROJECT_ID}}` placeholders during `scaffoldFullTemplates`. This means new repo installs get workflows with a hardcoded value in source-controlled files — brittle, hard to rotate, and inconsistent with how GitHub recommends storing non-secret configuration.
|
|
10
|
+
|
|
11
|
+
The fix: set `vars.PROJECT_ID` as a GitHub Actions Variable via the REST API during install, and have workflow templates source it from `vars` at runtime.
|
|
12
|
+
|
|
13
|
+
## Approach: env block sourced from `vars`
|
|
14
|
+
|
|
15
|
+
Keep the `PROJECT_ID:` entry in the workflow-level `env` block — just change its value from a hardcoded placeholder to `${{ vars.PROJECT_ID }}`. All `process.env.PROJECT_ID` references inside `actions/github-script` bodies remain unchanged. Minimal diff, maximum behavioral parity.
|
|
16
|
+
|
|
17
|
+
Rejected alternatives:
|
|
18
|
+
- **Inline `vars` everywhere**: larger diff, all script bodies change, no benefit.
|
|
19
|
+
- **Set vars + keep YAML hardcode**: adds complexity, defeats the goal.
|
|
20
|
+
|
|
21
|
+
## Changes
|
|
22
|
+
|
|
23
|
+
### 1. Templates — 3 workflow files
|
|
24
|
+
|
|
25
|
+
Files: `templates/.github/workflows/project-automation.yml`, `add-to-board.yml`, `agent-trigger.yml`
|
|
26
|
+
|
|
27
|
+
Every occurrence of:
|
|
28
|
+
|
|
29
|
+
| Before | After |
|
|
30
|
+
|---|---|
|
|
31
|
+
| `PROJECT_ID: "{{PROJECT_ID}}"` | `PROJECT_ID: ${{ vars.PROJECT_ID }}` |
|
|
32
|
+
| `env.PROJECT_ID != '{{PROJECT_ID}}'` | `env.PROJECT_ID != ''` |
|
|
33
|
+
|
|
34
|
+
Guard correctness: when `vars.PROJECT_ID` is unset, `${{ vars.PROJECT_ID }}` resolves to `''` at workflow startup → `env.PROJECT_ID != ''` suppresses execution cleanly. Verified: `vars.*` in workflow-level `env` block resolves before jobs run.
|
|
35
|
+
|
|
36
|
+
`pdlc.md` keeps `{{PROJECT_ID}}` substitution — it is a documentation file, not a YAML workflow.
|
|
37
|
+
|
|
38
|
+
### 2. `bin/cli.js` — new helper `setActionsVariable`
|
|
39
|
+
|
|
40
|
+
```javascript
|
|
41
|
+
function setActionsVariable(repo, name, value) {
|
|
42
|
+
try {
|
|
43
|
+
execFileSync('gh', ['api', `repos/${repo}/actions/variables/${name}`,
|
|
44
|
+
'--method', 'PATCH', '-f', `name=${name}`, '-f', `value=${value}`],
|
|
45
|
+
{ stdio: ['ignore', 'pipe', 'pipe'] });
|
|
46
|
+
} catch (err) {
|
|
47
|
+
const msg = err.stderr?.toString() || '';
|
|
48
|
+
if (msg.includes('404') || msg.includes('Not Found')) {
|
|
49
|
+
execFileSync('gh', ['api', `repos/${repo}/actions/variables`,
|
|
50
|
+
'--method', 'POST', '-f', `name=${name}`, '-f', `value=${value}`],
|
|
51
|
+
{ stdio: ['ignore', 'pipe', 'pipe'] });
|
|
52
|
+
} else {
|
|
53
|
+
throw err; // 403 bubbles up → caller emits user-visible warning
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Uses `gh api` via `execFileSync` — consistent with all other API calls in `cli.js`. PATCH on existing variable, POST on 404, throws on 403 so the caller can warn the user.
|
|
60
|
+
|
|
61
|
+
**Token scope requirement:** fine-grained PAT needs `variables:write`; classic PAT needs `repo` scope. `GITHUB_TOKEN` (workflow-issued) will return 403 — cannot set repo variables. The installer already calls `gh auth token` for `PROJECT_PAT`; the same authenticated session is used here.
|
|
62
|
+
|
|
63
|
+
### 3. `bin/cli.js` — `scaffoldFullTemplates` (line 345)
|
|
64
|
+
|
|
65
|
+
Remove the single line that substitutes `{{PROJECT_ID}}` in `project-automation.yml`:
|
|
66
|
+
|
|
67
|
+
```javascript
|
|
68
|
+
// REMOVE this line:
|
|
69
|
+
if (projectId) wfContent = wfContent.replace(/\{\{PROJECT_ID\}\}/g, () => projectId);
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
All other substitutions on lines 346–355 (`STATUS_FIELD_ID`, `ID_BRAINSTORMING`, `ID_DETAILING`, etc.) are preserved unchanged.
|
|
73
|
+
|
|
74
|
+
### 4. Create flow — call site
|
|
75
|
+
|
|
76
|
+
Inside the existing `if (projectId)` block (after `PROJECT_PAT` secret is set, ~line 541):
|
|
77
|
+
|
|
78
|
+
```javascript
|
|
79
|
+
try {
|
|
80
|
+
setActionsVariable(repo, 'PROJECT_ID', projectId);
|
|
81
|
+
console.log(`${green}✅ vars.PROJECT_ID set as Actions Variable.${reset}`);
|
|
82
|
+
} catch (_) {
|
|
83
|
+
console.log(`${yellow}⚠️ Could not set vars.PROJECT_ID — token may lack variables:write scope.\n Set it manually: repo Settings → Secrets and variables → Variables → PROJECT_ID = ${projectId}${reset}`);
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Non-fatal. Install continues regardless.
|
|
88
|
+
|
|
89
|
+
### 5. `--update` flow — same call site
|
|
90
|
+
|
|
91
|
+
The `--update` command is a **lite → full upgrade** — it exits early if the profile is already `full` (line 865). For lite → full, a new board is created via `createProjectV2` (line 920), producing a fresh `projectId`. Call `setActionsVariable` in the same `if (projectId)` block after board creation. Identical error handling to the create flow.
|
|
92
|
+
|
|
93
|
+
No "find existing project" query is needed. `projectId` is always available from the mutation result in both flows.
|
|
94
|
+
|
|
95
|
+
## Acceptance Criteria
|
|
96
|
+
|
|
97
|
+
- `npx create-agentic-pdlc` → `vars.PROJECT_ID` set as GitHub Actions Variable on the target repo
|
|
98
|
+
- No `PVT_xxx` value appears in any installed YAML file
|
|
99
|
+
- `resolve-ids` job resolves column IDs without any YAML modification
|
|
100
|
+
- `--update` (lite → full) → `vars.PROJECT_ID` set with the newly created board ID
|
|
101
|
+
- If token lacks scope → installer prints actionable warning with manual steps; install does not abort
|
|
102
|
+
|
|
103
|
+
## Out of Scope
|
|
104
|
+
|
|
105
|
+
- Migrating existing full installs (board already set up, YAML already hardcoded)
|
|
106
|
+
- Changing how `PROJECT_ID` is resolved during install (GraphQL flow unchanged)
|
|
107
|
+
- `--update` on an already-full install (exits early before any board logic runs)
|
|
108
|
+
|
|
109
|
+
## Files to Modify
|
|
110
|
+
|
|
111
|
+
- `templates/.github/workflows/project-automation.yml`
|
|
112
|
+
- `templates/.github/workflows/add-to-board.yml`
|
|
113
|
+
- `templates/.github/workflows/agent-trigger.yml`
|
|
114
|
+
- `bin/cli.js` — `setActionsVariable` helper, `scaffoldFullTemplates` line 345, create flow call site, update flow call site
|
package/package.json
CHANGED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Single source of truth for label → board column classification.
|
|
2
|
+
// Used by the event dispatcher (project-automation.yml) and the reconciliation cron (board-reconciliation.yml).
|
|
3
|
+
// pr:* beats stage:* — a live PR is higher-confidence signal than a stage label that may not have been cleaned up.
|
|
4
|
+
const LABEL_PRIORITY = [
|
|
5
|
+
{ label: 'pr:in-review', column: 'code_review_pr' },
|
|
6
|
+
{ label: 'pr:approved', column: 'code_review_pr' },
|
|
7
|
+
{ label: 'stage:development', column: 'development' },
|
|
8
|
+
{ label: 'stage:approval', column: 'approval' },
|
|
9
|
+
{ label: 'stage:detailing', column: 'detailing' },
|
|
10
|
+
{ label: 'stage:brainstorming', column: 'brainstorming' },
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
function classifyItem(labelNames) {
|
|
14
|
+
for (const { label, column } of LABEL_PRIORITY) {
|
|
15
|
+
if (labelNames.includes(label)) return column;
|
|
16
|
+
}
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
module.exports = { classifyItem, LABEL_PRIORITY };
|
|
@@ -5,7 +5,7 @@ on:
|
|
|
5
5
|
types: [opened]
|
|
6
6
|
|
|
7
7
|
env:
|
|
8
|
-
PROJECT_ID:
|
|
8
|
+
PROJECT_ID: ${{ vars.PROJECT_ID }}
|
|
9
9
|
STATUS_FIELD_ID: "{{STATUS_FIELD_ID}}"
|
|
10
10
|
STATUS_IDEA: "{{ID_IDEA}}"
|
|
11
11
|
|
|
@@ -17,7 +17,7 @@ jobs:
|
|
|
17
17
|
PROJECT_TOKEN: ${{ secrets.PROJECT_TOKEN }}
|
|
18
18
|
steps:
|
|
19
19
|
- name: Add issue to project board
|
|
20
|
-
if: ${{ env.PROJECT_TOKEN != '' && env.PROJECT_ID != '
|
|
20
|
+
if: ${{ env.PROJECT_TOKEN != '' && env.PROJECT_ID != '' }}
|
|
21
21
|
uses: actions/github-script@v8
|
|
22
22
|
with:
|
|
23
23
|
github-token: ${{ env.PROJECT_TOKEN }}
|
|
@@ -18,7 +18,7 @@ jobs:
|
|
|
18
18
|
contents: read
|
|
19
19
|
env:
|
|
20
20
|
PROJECT_TOKEN: ${{ secrets.PROJECT_TOKEN }}
|
|
21
|
-
PROJECT_ID:
|
|
21
|
+
PROJECT_ID: ${{ vars.PROJECT_ID }}
|
|
22
22
|
STATUS_FIELD_ID: "{{STATUS_FIELD_ID}}"
|
|
23
23
|
STATUS_DEVELOPMENT: "{{ID_DEVELOPMENT}}"
|
|
24
24
|
steps:
|
|
@@ -62,7 +62,7 @@ jobs:
|
|
|
62
62
|
});
|
|
63
63
|
|
|
64
64
|
- name: Move board card to Development
|
|
65
|
-
if: ${{ env.PROJECT_TOKEN != '' && env.PROJECT_ID != '
|
|
65
|
+
if: ${{ env.PROJECT_TOKEN != '' && env.PROJECT_ID != '' }}
|
|
66
66
|
continue-on-error: true
|
|
67
67
|
uses: actions/github-script@v8
|
|
68
68
|
with:
|
|
@@ -6,7 +6,7 @@ on:
|
|
|
6
6
|
- cron: '0 8 * * 1' # Every Monday at 8am
|
|
7
7
|
|
|
8
8
|
env:
|
|
9
|
-
PROJECT_ID:
|
|
9
|
+
PROJECT_ID: ${{ vars.PROJECT_ID }}
|
|
10
10
|
STATUS_FIELD_ID: "{{STATUS_FIELD_ID}}"
|
|
11
11
|
STATUS_BRAINSTORMING: "{{ID_BRAINSTORMING}}"
|
|
12
12
|
STATUS_DETAILING: "{{ID_DETAILING}}"
|
|
@@ -26,7 +26,7 @@ jobs:
|
|
|
26
26
|
issues: write
|
|
27
27
|
steps:
|
|
28
28
|
- name: Validate Board Configuration
|
|
29
|
-
if: ${{ env.PROJECT_TOKEN != '' && env.PROJECT_ID != '
|
|
29
|
+
if: ${{ env.PROJECT_TOKEN != '' && env.PROJECT_ID != '' }}
|
|
30
30
|
uses: actions/github-script@v8
|
|
31
31
|
with:
|
|
32
32
|
github-token: ${{ env.PROJECT_TOKEN }}
|
|
@@ -9,7 +9,7 @@ on:
|
|
|
9
9
|
types: [labeled, edited, closed]
|
|
10
10
|
|
|
11
11
|
env:
|
|
12
|
-
PROJECT_ID:
|
|
12
|
+
PROJECT_ID: ${{ vars.PROJECT_ID }}
|
|
13
13
|
STATUS_FIELD_ID: "{{STATUS_FIELD_ID}}"
|
|
14
14
|
STATUS_IDEA: "{{ID_IDEA}}"
|
|
15
15
|
STATUS_BRAINSTORMING: "{{ID_BRAINSTORMING}}"
|
|
@@ -30,7 +30,7 @@ jobs:
|
|
|
30
30
|
PROJECT_TOKEN: ${{ secrets.PROJECT_TOKEN }}
|
|
31
31
|
steps:
|
|
32
32
|
- name: Detect Label and Move Issue
|
|
33
|
-
if: ${{ env.PROJECT_TOKEN != '' && env.PROJECT_ID != '
|
|
33
|
+
if: ${{ env.PROJECT_TOKEN != '' && env.PROJECT_ID != '' }}
|
|
34
34
|
uses: actions/github-script@v8
|
|
35
35
|
with:
|
|
36
36
|
github-token: ${{ env.PROJECT_TOKEN }}
|
|
@@ -91,7 +91,7 @@ jobs:
|
|
|
91
91
|
PROJECT_TOKEN: ${{ secrets.PROJECT_TOKEN }}
|
|
92
92
|
steps:
|
|
93
93
|
- name: Check spec markers and swap labels
|
|
94
|
-
if: ${{ env.PROJECT_TOKEN != '' && env.PROJECT_ID != '
|
|
94
|
+
if: ${{ env.PROJECT_TOKEN != '' && env.PROJECT_ID != '' }}
|
|
95
95
|
uses: actions/github-script@v8
|
|
96
96
|
with:
|
|
97
97
|
github-token: ${{ env.PROJECT_TOKEN }}
|
|
@@ -149,7 +149,7 @@ jobs:
|
|
|
149
149
|
# runs-on: ubuntu-latest
|
|
150
150
|
# steps:
|
|
151
151
|
# - name: Move issue to Idea
|
|
152
|
-
# if: ${{ env.PROJECT_TOKEN != '' && env.PROJECT_ID != '
|
|
152
|
+
# if: ${{ env.PROJECT_TOKEN != '' && env.PROJECT_ID != '' }}
|
|
153
153
|
# uses: actions/github-script@v8
|
|
154
154
|
# with:
|
|
155
155
|
# github-token: ${{ env.PROJECT_TOKEN }}
|
|
@@ -179,7 +179,7 @@ jobs:
|
|
|
179
179
|
PROJECT_TOKEN: ${{ secrets.PROJECT_TOKEN }}
|
|
180
180
|
steps:
|
|
181
181
|
- name: Move linked issue to Testing
|
|
182
|
-
if: ${{ env.PROJECT_TOKEN != '' && env.PROJECT_ID != '
|
|
182
|
+
if: ${{ env.PROJECT_TOKEN != '' && env.PROJECT_ID != '' }}
|
|
183
183
|
uses: actions/github-script@v8
|
|
184
184
|
with:
|
|
185
185
|
github-token: ${{ env.PROJECT_TOKEN }}
|
|
@@ -233,7 +233,7 @@ jobs:
|
|
|
233
233
|
PROJECT_TOKEN: ${{ secrets.PROJECT_TOKEN }}
|
|
234
234
|
steps:
|
|
235
235
|
- name: Move linked issue to Code Review / PR
|
|
236
|
-
if: ${{ env.PROJECT_TOKEN != '' && env.PROJECT_ID != '
|
|
236
|
+
if: ${{ env.PROJECT_TOKEN != '' && env.PROJECT_ID != '' }}
|
|
237
237
|
uses: actions/github-script@v8
|
|
238
238
|
with:
|
|
239
239
|
github-token: ${{ env.PROJECT_TOKEN }}
|
|
@@ -281,7 +281,7 @@ jobs:
|
|
|
281
281
|
PROJECT_TOKEN: ${{ secrets.PROJECT_TOKEN }}
|
|
282
282
|
steps:
|
|
283
283
|
- name: Swap PR labels
|
|
284
|
-
if: ${{ env.PROJECT_TOKEN != '' && env.PROJECT_ID != '
|
|
284
|
+
if: ${{ env.PROJECT_TOKEN != '' && env.PROJECT_ID != '' }}
|
|
285
285
|
uses: actions/github-script@v8
|
|
286
286
|
with:
|
|
287
287
|
github-token: ${{ env.PROJECT_TOKEN }}
|
|
@@ -300,7 +300,7 @@ jobs:
|
|
|
300
300
|
PROJECT_TOKEN: ${{ secrets.PROJECT_TOKEN }}
|
|
301
301
|
steps:
|
|
302
302
|
- name: Move issue to Production
|
|
303
|
-
if: ${{ env.PROJECT_TOKEN != '' && env.PROJECT_ID != '
|
|
303
|
+
if: ${{ env.PROJECT_TOKEN != '' && env.PROJECT_ID != '' }}
|
|
304
304
|
uses: actions/github-script@v8
|
|
305
305
|
with:
|
|
306
306
|
github-token: ${{ env.PROJECT_TOKEN }}
|
|
@@ -359,3 +359,42 @@ jobs:
|
|
|
359
359
|
await github.rest.issues.removeLabel({ owner, repo, issue_number, name: label }).catch(() => {});
|
|
360
360
|
}
|
|
361
361
|
console.log(`Issue #${issue_number} labels cleaned up`);
|
|
362
|
+
|
|
363
|
+
move-card-on-issue-close:
|
|
364
|
+
name: Closed issue → Archive from board
|
|
365
|
+
if: github.event_name == 'issues' && github.event.action == 'closed'
|
|
366
|
+
runs-on: ubuntu-latest
|
|
367
|
+
env:
|
|
368
|
+
PROJECT_TOKEN: ${{ secrets.PROJECT_TOKEN }}
|
|
369
|
+
steps:
|
|
370
|
+
- name: Archive board card
|
|
371
|
+
if: ${{ env.PROJECT_TOKEN != '' && env.PROJECT_ID != '' }}
|
|
372
|
+
uses: actions/github-script@v8
|
|
373
|
+
with:
|
|
374
|
+
github-token: ${{ env.PROJECT_TOKEN }}
|
|
375
|
+
script: |
|
|
376
|
+
const nodeId = context.payload.issue.node_id;
|
|
377
|
+
let itemId;
|
|
378
|
+
try {
|
|
379
|
+
const added = await github.graphql(`
|
|
380
|
+
mutation($p: ID!, $c: ID!) {
|
|
381
|
+
addProjectV2ItemById(input: {projectId: $p, contentId: $c}) { item { id } }
|
|
382
|
+
}`, { p: process.env.PROJECT_ID, c: nodeId });
|
|
383
|
+
itemId = added.addProjectV2ItemById.item.id;
|
|
384
|
+
if (!itemId) {
|
|
385
|
+
console.log(`Could not extract itemId from add response`);
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
} catch (e) {
|
|
389
|
+
console.log(`Could not add issue to project: ${e.message}`);
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
try {
|
|
393
|
+
await github.graphql(`
|
|
394
|
+
mutation($p: ID!, $i: ID!) {
|
|
395
|
+
archiveProjectV2Item(input: {projectId: $p, itemId: $i}) { item { id } }
|
|
396
|
+
}`, { p: process.env.PROJECT_ID, i: itemId });
|
|
397
|
+
console.log(`Issue #${context.payload.issue.number} archived from board`);
|
|
398
|
+
} catch (e) {
|
|
399
|
+
console.log(`Could not archive item: ${e.message}`);
|
|
400
|
+
}
|