create-agentic-pdlc 3.0.0 → 3.1.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/.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 +65 -11
- 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
package/bin/cli.js
CHANGED
|
@@ -60,6 +60,7 @@ const i18n = {
|
|
|
60
60
|
cursor_rules_written: t('✅ Default cursor rules written to .cursorrules', '✅ Regras padrão do cursor salvas em .cursorrules', '✅ Reglas por defecto de cursor guardadas en .cursorrules'),
|
|
61
61
|
setup_done: t('🎉 All set! Continue the setup with your agent:', '🎉 Aqui tá pronto! Continue o setup com o seu agente:', '🎉 ¡Listo! Continúa el setup con tu agente:'),
|
|
62
62
|
setup_done_hint: t('>>> Tell it to read and execute the .agentic-setup.md file!', '>>> Diga a ele para ler e executar o arquivo .agentic-setup.md!', '>>> Dile que lea y ejecute el archivo .agentic-setup.md!'),
|
|
63
|
+
upgrade_hint: t('💡 To add the full board + multi-agent automation later: npx create-agentic-pdlc --upgrade-to-agentic', '💡 Para adicionar o board completo + automação multi-agente mais tarde: npx create-agentic-pdlc --upgrade-to-agentic', '💡 Para agregar el tablero completo + automatización multi-agente más tarde: npx create-agentic-pdlc --upgrade-to-agentic'),
|
|
63
64
|
update_title: t('agentic-pdlc — Agent Configuration Status', 'agentic-pdlc — Status de Configuração dos Agentes', 'agentic-pdlc — Estado de Configuración de Agentes'),
|
|
64
65
|
update_no_context: t('❌ No .agentic-pdlc/cli-context.json found. Run npx create-agentic-pdlc first.', '❌ Arquivo .agentic-pdlc/cli-context.json não encontrado. Rode npx create-agentic-pdlc primeiro.', '❌ Archivo .agentic-pdlc/cli-context.json no encontrado. Ejecuta npx create-agentic-pdlc primero.'),
|
|
65
66
|
update_all_ok: t('All agents configured!', 'Todos os agentes configurados!', '¡Todos los agentes configurados!'),
|
|
@@ -80,6 +81,16 @@ const i18n = {
|
|
|
80
81
|
'✅ Issue templates copiados para .github/ISSUE_TEMPLATE/',
|
|
81
82
|
'✅ Issue templates copiados a .github/ISSUE_TEMPLATE/'
|
|
82
83
|
),
|
|
84
|
+
vars_project_id_ok: t(
|
|
85
|
+
'✅ vars.PROJECT_ID set as Actions Variable.',
|
|
86
|
+
'✅ vars.PROJECT_ID configurado como Variável do Actions.',
|
|
87
|
+
'✅ vars.PROJECT_ID configurado como Variable de Actions.'
|
|
88
|
+
),
|
|
89
|
+
vars_project_id_warn: t(
|
|
90
|
+
'⚠️ Could not set vars.PROJECT_ID — token may lack variables:write scope.\n Set manually: repo Settings → Secrets and variables → Variables → PROJECT_ID = ',
|
|
91
|
+
'⚠️ Não foi possível configurar vars.PROJECT_ID — o token pode não ter permissão variables:write.\n Configure manualmente: repo Settings → Secrets and variables → Variables → PROJECT_ID = ',
|
|
92
|
+
'⚠️ No se pudo configurar vars.PROJECT_ID — el token puede no tener permiso variables:write.\n Configura manualmente: repo Settings → Secrets and variables → Variables → PROJECT_ID = '
|
|
93
|
+
),
|
|
83
94
|
};
|
|
84
95
|
|
|
85
96
|
const cyan = '\x1b[36m';
|
|
@@ -282,6 +293,29 @@ function scaffoldLiteTemplates(sourceDir, targetDir) {
|
|
|
282
293
|
}
|
|
283
294
|
}
|
|
284
295
|
|
|
296
|
+
function setActionsVariable(repo, name, value, execFn = execFileSync) {
|
|
297
|
+
try {
|
|
298
|
+
execFn('gh', [
|
|
299
|
+
'api', `repos/${repo}/actions/variables/${name}`,
|
|
300
|
+
'--method', 'PATCH',
|
|
301
|
+
'-f', `name=${name}`,
|
|
302
|
+
'-f', `value=${value}`
|
|
303
|
+
], { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
304
|
+
} catch (err) {
|
|
305
|
+
const msg = (err.stderr?.toString() || '') + (err.message || '');
|
|
306
|
+
if (msg.includes('404') || msg.includes('Not Found')) {
|
|
307
|
+
execFn('gh', [
|
|
308
|
+
'api', `repos/${repo}/actions/variables`,
|
|
309
|
+
'--method', 'POST',
|
|
310
|
+
'-f', `name=${name}`,
|
|
311
|
+
'-f', `value=${value}`
|
|
312
|
+
], { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
313
|
+
} else {
|
|
314
|
+
throw err;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
285
319
|
function scaffoldFullTemplates(sourceDir, targetDir, projectId, statusFieldId, optionMap, repoOwner, repoName) {
|
|
286
320
|
const destTemplates = path.join(targetDir, '.agentic-pdlc', 'templates');
|
|
287
321
|
fs.mkdirSync(destTemplates, { recursive: true });
|
|
@@ -342,7 +376,6 @@ function scaffoldFullTemplates(sourceDir, targetDir, projectId, statusFieldId, o
|
|
|
342
376
|
const paPath = path.join(destTemplates, '.github', 'workflows', 'project-automation.yml');
|
|
343
377
|
if (fs.existsSync(paPath) && Object.keys(optionMap).length > 0) {
|
|
344
378
|
let wfContent = fs.readFileSync(paPath, 'utf8');
|
|
345
|
-
if (projectId) wfContent = wfContent.replace(/\{\{PROJECT_ID\}\}/g, () => projectId);
|
|
346
379
|
if (statusFieldId) wfContent = wfContent.replace(/\{\{STATUS_FIELD_ID\}\}/g, () => statusFieldId);
|
|
347
380
|
wfContent = wfContent.replace(/\{\{ID_IDEA\}\}/g, () => optionMap['💡 Idea - No move to Exploration directly'] || 'MISSING_ID');
|
|
348
381
|
wfContent = wfContent.replace(/\{\{ID_EXPLORATION\}\}/g, () => optionMap['🔍 Exploration'] || 'MISSING_ID');
|
|
@@ -536,21 +569,31 @@ async function runFullSetup() {
|
|
|
536
569
|
}
|
|
537
570
|
}
|
|
538
571
|
|
|
539
|
-
// Auto-provision
|
|
572
|
+
// Auto-provision PROJECT_TOKEN for personal repos
|
|
540
573
|
let patAutoSet = false;
|
|
541
574
|
if (projectId && !isOrg) {
|
|
542
575
|
try {
|
|
543
576
|
const tokenOut = execFileSync('gh', ['auth', 'token'], { stdio: ['ignore', 'pipe', 'pipe'], encoding: 'utf8' }).trim();
|
|
544
577
|
if (tokenOut) {
|
|
545
|
-
execFileSync('gh', ['secret', 'set', '
|
|
578
|
+
execFileSync('gh', ['secret', 'set', 'PROJECT_TOKEN', '--body', tokenOut, '--repo', repo], { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
546
579
|
patAutoSet = true;
|
|
547
|
-
console.log(`\n${green}✅
|
|
580
|
+
console.log(`\n${green}✅ PROJECT_TOKEN secret set automatically (uses your gh OAuth token).${reset}`);
|
|
548
581
|
}
|
|
549
582
|
} catch (err) {
|
|
550
|
-
console.log(`\n${yellow}⚠️ Could not auto-set
|
|
583
|
+
console.log(`\n${yellow}⚠️ Could not auto-set PROJECT_TOKEN. Agent will guide manual setup.${reset}`);
|
|
551
584
|
}
|
|
552
585
|
} else if (projectId && isOrg) {
|
|
553
|
-
console.log(`\n${yellow}ℹ️ Org repo detected —
|
|
586
|
+
console.log(`\n${yellow}ℹ️ Org repo detected — PROJECT_TOKEN will require manual setup for security.${reset}`);
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// Set PROJECT_ID as GitHub Actions Variable
|
|
590
|
+
if (projectId) {
|
|
591
|
+
try {
|
|
592
|
+
setActionsVariable(repo, 'PROJECT_ID', projectId);
|
|
593
|
+
console.log(`${green}${i18n.vars_project_id_ok}${reset}`);
|
|
594
|
+
} catch (_) {
|
|
595
|
+
console.log(`${yellow}${i18n.vars_project_id_warn}${projectId}${reset}`);
|
|
596
|
+
}
|
|
554
597
|
}
|
|
555
598
|
|
|
556
599
|
await setBranchProtection(repo, ['PDLC Stage Gate', 'QA Gate']);
|
|
@@ -790,7 +833,7 @@ function resolveMode(args) {
|
|
|
790
833
|
}
|
|
791
834
|
|
|
792
835
|
// Export for testing
|
|
793
|
-
if (typeof module !== 'undefined') module.exports = { resolveMode };
|
|
836
|
+
if (typeof module !== 'undefined') module.exports = { resolveMode, setActionsVariable, scaffoldLiteTemplates, scaffoldFullTemplates };
|
|
794
837
|
|
|
795
838
|
// ─── runLiteSetup ─────────────────────────────────────────────────────────────
|
|
796
839
|
|
|
@@ -849,6 +892,7 @@ async function runLiteSetup() {
|
|
|
849
892
|
});
|
|
850
893
|
|
|
851
894
|
copyAdapterFiles(agent, sourceDir, targetDir);
|
|
895
|
+
console.log(`${cyan}${i18n.upgrade_hint}${reset}`);
|
|
852
896
|
|
|
853
897
|
rl.close();
|
|
854
898
|
}
|
|
@@ -988,20 +1032,30 @@ async function runUpgradeToAgentic() {
|
|
|
988
1032
|
}
|
|
989
1033
|
}
|
|
990
1034
|
|
|
991
|
-
// Auto-provision
|
|
1035
|
+
// Auto-provision PROJECT_TOKEN for personal repos
|
|
992
1036
|
let patAutoSet = false;
|
|
993
1037
|
if (projectId && !isOrg) {
|
|
994
1038
|
try {
|
|
995
1039
|
const tokenOut = execFileSync('gh', ['auth', 'token'],
|
|
996
1040
|
{ stdio: ['ignore', 'pipe', 'pipe'], encoding: 'utf8' }).trim();
|
|
997
1041
|
if (tokenOut) {
|
|
998
|
-
execFileSync('gh', ['secret', 'set', '
|
|
1042
|
+
execFileSync('gh', ['secret', 'set', 'PROJECT_TOKEN', '--body', tokenOut, '--repo', repo],
|
|
999
1043
|
{ stdio: ['ignore', 'pipe', 'pipe'] });
|
|
1000
1044
|
patAutoSet = true;
|
|
1001
|
-
console.log(`\n${green}✅
|
|
1045
|
+
console.log(`\n${green}✅ PROJECT_TOKEN secret set automatically (uses your gh OAuth token).${reset}`);
|
|
1002
1046
|
}
|
|
1003
1047
|
} catch (_) {
|
|
1004
|
-
console.log(`\n${yellow}⚠️ Could not auto-set
|
|
1048
|
+
console.log(`\n${yellow}⚠️ Could not auto-set PROJECT_TOKEN. Agent will guide manual setup.${reset}`);
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
// Set PROJECT_ID as GitHub Actions Variable
|
|
1053
|
+
if (projectId) {
|
|
1054
|
+
try {
|
|
1055
|
+
setActionsVariable(repo, 'PROJECT_ID', projectId);
|
|
1056
|
+
console.log(`${green}${i18n.vars_project_id_ok}${reset}`);
|
|
1057
|
+
} catch (_) {
|
|
1058
|
+
console.log(`${yellow}${i18n.vars_project_id_warn}${projectId}${reset}`);
|
|
1005
1059
|
}
|
|
1006
1060
|
}
|
|
1007
1061
|
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# Archive Board Card on Issue Close 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:** Archive the GitHub Project board card when an issue is closed, preventing stale cards from accumulating in active columns.
|
|
6
|
+
|
|
7
|
+
**Architecture:** Single new job appended to `project-automation.yml` template. Triggers on the already-present `issues: closed` event. Uses `addProjectV2ItemById` (idempotent) then `archiveProjectV2Item` — handles both issues closed without a PR and issues closed via PR merge safely.
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** GitHub Actions, `actions/github-script@v8`, GitHub GraphQL API (`addProjectV2ItemById`, `archiveProjectV2Item`).
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## File Map
|
|
14
|
+
|
|
15
|
+
| Action | Path | Responsibility |
|
|
16
|
+
|---|---|---|
|
|
17
|
+
| Modify | `templates/.github/workflows/project-automation.yml` | Add `move-card-on-issue-close` job at end of file |
|
|
18
|
+
|
|
19
|
+
No unit tests — YAML workflow template; correctness verified by grep and manual inspection.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
### Task 1: Add `move-card-on-issue-close` job
|
|
24
|
+
|
|
25
|
+
**Files:**
|
|
26
|
+
- Modify: `templates/.github/workflows/project-automation.yml` (append after `cleanup-labels-on-close`)
|
|
27
|
+
|
|
28
|
+
- [ ] **Step 1: Verify the insertion point**
|
|
29
|
+
|
|
30
|
+
Run:
|
|
31
|
+
```bash
|
|
32
|
+
tail -5 templates/.github/workflows/project-automation.yml
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Expected: last line is `console.log(\`Issue #\${issue_number} labels cleaned up\`);` followed by closing braces. The file ends after `cleanup-labels-on-close`.
|
|
36
|
+
|
|
37
|
+
- [ ] **Step 2: Append the new job**
|
|
38
|
+
|
|
39
|
+
At the end of `templates/.github/workflows/project-automation.yml`, add:
|
|
40
|
+
|
|
41
|
+
```yaml
|
|
42
|
+
|
|
43
|
+
move-card-on-issue-close:
|
|
44
|
+
name: Closed issue → Archive from board
|
|
45
|
+
if: github.event_name == 'issues' && github.event.action == 'closed'
|
|
46
|
+
runs-on: ubuntu-latest
|
|
47
|
+
env:
|
|
48
|
+
PROJECT_TOKEN: ${{ secrets.PROJECT_TOKEN }}
|
|
49
|
+
steps:
|
|
50
|
+
- name: Archive board card
|
|
51
|
+
if: ${{ env.PROJECT_TOKEN != '' && env.PROJECT_ID != '' }}
|
|
52
|
+
uses: actions/github-script@v8
|
|
53
|
+
with:
|
|
54
|
+
github-token: ${{ env.PROJECT_TOKEN }}
|
|
55
|
+
script: |
|
|
56
|
+
const nodeId = context.payload.issue.node_id;
|
|
57
|
+
let itemId;
|
|
58
|
+
try {
|
|
59
|
+
const added = await github.graphql(`
|
|
60
|
+
mutation($p: ID!, $c: ID!) {
|
|
61
|
+
addProjectV2ItemById(input: {projectId: $p, contentId: $c}) { item { id } }
|
|
62
|
+
}`, { p: process.env.PROJECT_ID, c: nodeId });
|
|
63
|
+
itemId = added.addProjectV2ItemById.item.id;
|
|
64
|
+
} catch (e) {
|
|
65
|
+
console.log(`Could not add issue to project: ${e.message}`);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
await github.graphql(`
|
|
69
|
+
mutation($p: ID!, $i: ID!) {
|
|
70
|
+
archiveProjectV2Item(input: {projectId: $p, itemId: $i}) { item { id } }
|
|
71
|
+
}`, { p: process.env.PROJECT_ID, i: itemId });
|
|
72
|
+
console.log(`Issue #${context.payload.issue.number} archived from board`);
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
- [ ] **Step 3: Verify job was added and file is syntactically correct**
|
|
76
|
+
|
|
77
|
+
Run:
|
|
78
|
+
```bash
|
|
79
|
+
grep -n "move-card-on-issue-close\|archiveProjectV2Item" templates/.github/workflows/project-automation.yml
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Expected: two matches — the job name line and the mutation name.
|
|
83
|
+
|
|
84
|
+
Run:
|
|
85
|
+
```bash
|
|
86
|
+
python3 -c "import yaml, sys; yaml.safe_load(open('templates/.github/workflows/project-automation.yml'))" 2>&1
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Expected: no output (valid YAML).
|
|
90
|
+
|
|
91
|
+
- [ ] **Step 4: Verify guard condition matches existing pattern**
|
|
92
|
+
|
|
93
|
+
Run:
|
|
94
|
+
```bash
|
|
95
|
+
grep "PROJECT_TOKEN != ''" templates/.github/workflows/project-automation.yml
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Expected: multiple lines including the new job — confirms guard is consistent with other jobs.
|
|
99
|
+
|
|
100
|
+
- [ ] **Step 5: Commit**
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
git add templates/.github/workflows/project-automation.yml
|
|
104
|
+
git commit -m "feat(templates): archive board card when issue is closed"
|
|
105
|
+
```
|
|
@@ -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 ✓
|