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
|
@@ -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
|
+
}
|
package/tests/cli.test.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
const { describe, it } = require('node:test');
|
|
2
2
|
const assert = require('node:assert/strict');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const crypto = require('crypto');
|
|
3
7
|
|
|
4
8
|
const { resolveMode } = require('../bin/cli.js');
|
|
5
9
|
|
|
@@ -30,3 +34,85 @@ describe('buildFullClaudeContent', () => {
|
|
|
30
34
|
assert.ok(result.includes('## Extra'));
|
|
31
35
|
});
|
|
32
36
|
});
|
|
37
|
+
|
|
38
|
+
describe('setActionsVariable', () => {
|
|
39
|
+
it('calls PATCH first', () => {
|
|
40
|
+
const calls = [];
|
|
41
|
+
const execFn = (cmd, args, _opts) => { calls.push([...args]); };
|
|
42
|
+
const { setActionsVariable } = require('../bin/cli.js');
|
|
43
|
+
setActionsVariable('owner/repo', 'PROJECT_ID', 'PVT_abc', execFn);
|
|
44
|
+
assert.equal(calls.length, 1);
|
|
45
|
+
assert.ok(calls[0].includes('--method'));
|
|
46
|
+
assert.ok(calls[0].includes('PATCH'));
|
|
47
|
+
assert.ok(calls[0].some(a => a.includes('PROJECT_ID')));
|
|
48
|
+
assert.ok(calls[0].some(a => a.includes('PVT_abc')));
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('falls back to POST on 404', () => {
|
|
52
|
+
const calls = [];
|
|
53
|
+
let callCount = 0;
|
|
54
|
+
const execFn = (cmd, args, _opts) => {
|
|
55
|
+
calls.push([...args]);
|
|
56
|
+
callCount++;
|
|
57
|
+
if (callCount === 1) {
|
|
58
|
+
const err = new Error('Not Found');
|
|
59
|
+
err.stderr = Buffer.from('Not Found');
|
|
60
|
+
throw err;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
const { setActionsVariable } = require('../bin/cli.js');
|
|
64
|
+
setActionsVariable('owner/repo', 'PROJECT_ID', 'PVT_abc', execFn);
|
|
65
|
+
assert.equal(calls.length, 2);
|
|
66
|
+
assert.ok(calls[0].includes('PATCH'));
|
|
67
|
+
assert.ok(calls[0].some(a => a.includes('PVT_abc')));
|
|
68
|
+
assert.ok(calls[1].includes('POST'));
|
|
69
|
+
assert.ok(calls[1].some(a => a.includes('PVT_abc')));
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('throws on 403', () => {
|
|
73
|
+
const execFn = () => {
|
|
74
|
+
const err = new Error('Forbidden');
|
|
75
|
+
err.stderr = Buffer.from('Forbidden');
|
|
76
|
+
throw err;
|
|
77
|
+
};
|
|
78
|
+
const { setActionsVariable } = require('../bin/cli.js');
|
|
79
|
+
assert.throws(
|
|
80
|
+
() => setActionsVariable('owner/repo', 'PROJECT_ID', 'PVT_abc', execFn),
|
|
81
|
+
/Forbidden/
|
|
82
|
+
);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
describe('scaffoldLiteTemplates', () => {
|
|
87
|
+
it('copies CLAUDE.md and AGENTS.md but excludes .github/workflows', () => {
|
|
88
|
+
const { scaffoldLiteTemplates } = require('../bin/cli.js');
|
|
89
|
+
const src = path.join(__dirname, '..');
|
|
90
|
+
const tmp = path.join(os.tmpdir(), `pdlc-lite-${crypto.randomBytes(4).toString('hex')}`);
|
|
91
|
+
try {
|
|
92
|
+
scaffoldLiteTemplates(src, tmp);
|
|
93
|
+
const base = path.join(tmp, '.agentic-pdlc', 'templates');
|
|
94
|
+
assert.ok(fs.existsSync(path.join(base, 'CLAUDE.md')), 'CLAUDE.md should exist');
|
|
95
|
+
assert.ok(fs.existsSync(path.join(base, 'AGENTS.md')), 'AGENTS.md should exist');
|
|
96
|
+
assert.ok(!fs.existsSync(path.join(base, '.github', 'workflows')), '.github/workflows must not exist in lite');
|
|
97
|
+
} finally {
|
|
98
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe('scaffoldFullTemplates', () => {
|
|
104
|
+
it('copies .github/workflows with at least one yml file', () => {
|
|
105
|
+
const { scaffoldFullTemplates } = require('../bin/cli.js');
|
|
106
|
+
const src = path.join(__dirname, '..');
|
|
107
|
+
const tmp = path.join(os.tmpdir(), `pdlc-full-${crypto.randomBytes(4).toString('hex')}`);
|
|
108
|
+
try {
|
|
109
|
+
scaffoldFullTemplates(src, tmp, null, null, {}, 'owner', 'repo');
|
|
110
|
+
const wfDir = path.join(tmp, '.agentic-pdlc', 'templates', '.github', 'workflows');
|
|
111
|
+
assert.ok(fs.existsSync(wfDir), '.github/workflows should exist in full');
|
|
112
|
+
const ymls = fs.readdirSync(wfDir).filter(f => f.endsWith('.yml'));
|
|
113
|
+
assert.ok(ymls.length > 0, 'should contain at least one .yml file');
|
|
114
|
+
} finally {
|
|
115
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
});
|