create-agentic-pdlc 2.2.0 → 2.2.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/adapters/claude-code/skill.md +22 -12
- package/bin/cli.js +85 -6
- package/package.json +1 -1
|
@@ -25,7 +25,7 @@ If any of these files are missing, you are in **Setup Mode**. Do not proceed wit
|
|
|
25
25
|
1. **Language Detection:** Analyze the user's previous prompts and preferred language. Conduct this entire Setup Mode and ask all your interactive questions in that same language.
|
|
26
26
|
2. Acknowledge that the framework is not yet set up.
|
|
27
27
|
3. **Pre-filled Context:** Before asking any questions, read the following files if they exist:
|
|
28
|
-
- `.agentic-pdlc/cli-context.json` — written by the CLI. Contains `projectName`, `repoOwner`, `repoName
|
|
28
|
+
- `.agentic-pdlc/cli-context.json` — written by the CLI. Contains `projectName`, `repoOwner`, `repoName`, `projectNumber`, `isOrg`, `boardUrl`, and `patAutoSet` (boolean). Use these values directly and skip the corresponding questions. Honor `patAutoSet` in Step 7 and `boardUrl` in Step 10.
|
|
29
29
|
- `.agentic-pdlc/templates/docs/pdlc.md` — the CLI pre-fills PROJECT_ID, STATUS_FIELD_ID, REPO_OWNER, REPO_NAME, and all 9 column option IDs. If none of the values still contain `{{...}}` placeholders, skip the entire Board IDs question group.
|
|
30
30
|
- `.agentic-pdlc/templates/.github/workflows/project-automation.yml` — the CLI also pre-fills all ID placeholders here. When writing the workflow file, the remaining `{{...}}` placeholders are only non-ID ones (project name, commands, etc.).
|
|
31
31
|
4. Interactively ask the user only for the **missing values**, **one group at a time**:
|
|
@@ -50,23 +50,33 @@ If any of these files are missing, you are in **Setup Mode**. Do not proceed wit
|
|
|
50
50
|
- c) **Other** — *Enter the agent's handle.*
|
|
51
51
|
5. Generate and write the missing files replacing the `{{SCREAMING_SNAKE_CASE}}` placeholders using the templates in `.agentic-pdlc/templates/`.
|
|
52
52
|
6. Offer to run the `gh` commands for labels (`spec:approved`, `pr:in-review`, `pr:approved`, `architecture-violation`).
|
|
53
|
-
7.
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
53
|
+
7. **`PROJECT_PAT` secret (required for board automation):**
|
|
54
|
+
|
|
55
|
+
Read `patAutoSet` from `.agentic-pdlc/cli-context.json`:
|
|
56
|
+
|
|
57
|
+
**If `patAutoSet === true`:** The CLI already configured this secret automatically. Print `✅ PROJECT_PAT is configured.` and continue to Step 8 — do not ask the user anything.
|
|
58
|
+
|
|
59
|
+
**If `patAutoSet === false` (org repo):** Show the block below and wait for the user to reply "done" or "secret set" before continuing:
|
|
60
|
+
|
|
61
|
+
> Your repo is in an organization. For security, `PROJECT_PAT` must be a dedicated PAT (not your personal OAuth token). Without it, all board card movements in CI will silently skip — no error surfaced.
|
|
62
|
+
>
|
|
63
|
+
> 1. Open: **github.com/settings/tokens** → *Generate new token (classic)*
|
|
64
|
+
> 2. Name: `PROJECT_PAT — <repo-name>`
|
|
65
|
+
> 3. Select scopes: ✅ `repo` + ✅ `project`
|
|
66
|
+
> 4. Copy the token, then run:
|
|
67
|
+
> ```
|
|
68
|
+
> gh secret set PROJECT_PAT --body "<your-token>" --repo <owner>/<repo>
|
|
69
|
+
> ```
|
|
70
|
+
> 5. Reply **"done"** when finished.
|
|
62
71
|
8. **IMPORTANT:** Delete the setup prompt file by running exactly:
|
|
63
72
|
```
|
|
64
73
|
rm -f .agentic-setup.md .agentic-setup-prompt.md .agentic-pdlc/SETUP_PROMPT.md
|
|
65
74
|
```
|
|
66
75
|
**Do NOT run `git add` or any other git command.** These files were never committed and do not exist in the git index. This command must run **before** the commit step.
|
|
67
76
|
9. Commit everything with the message: `chore: setup agentic-pdlc framework`.
|
|
68
|
-
10. Conclude Setup Mode. Read `
|
|
69
|
-
|
|
77
|
+
10. Conclude Setup Mode. Read `boardUrl` from `.agentic-pdlc/cli-context.json` and show the user exactly this (do not reconstruct the URL — `boardUrl` already includes the correct `users/` or `orgs/` path segment and `?layout=board`):
|
|
78
|
+
|
|
79
|
+
`🎉 Setup complete! Your board: <boardUrl>`
|
|
70
80
|
|
|
71
81
|
---
|
|
72
82
|
|
package/bin/cli.js
CHANGED
|
@@ -84,6 +84,11 @@ console.log(`${cyan}============================================================
|
|
|
84
84
|
console.log(`${cyan}${i18n.welcome}${reset}`);
|
|
85
85
|
console.log(`${cyan}================================================================${reset}\n`);
|
|
86
86
|
|
|
87
|
+
function buildBoardUrl(repoOwner, projectNumber, isOrg) {
|
|
88
|
+
const segment = isOrg ? 'orgs' : 'users';
|
|
89
|
+
return `https://github.com/${segment}/${repoOwner}/projects/${projectNumber}/views/1?layout=board`;
|
|
90
|
+
}
|
|
91
|
+
|
|
87
92
|
// Helper function to recursively copy directories
|
|
88
93
|
function copyDirSync(src, dest) {
|
|
89
94
|
if (!fs.existsSync(dest)) fs.mkdirSync(dest, { recursive: true });
|
|
@@ -121,6 +126,43 @@ async function runSetup() {
|
|
|
121
126
|
process.exit(1);
|
|
122
127
|
}
|
|
123
128
|
|
|
129
|
+
function getScopes() {
|
|
130
|
+
try {
|
|
131
|
+
const out = execFileSync('gh', ['api', 'user', '-i'], { stdio: ['ignore', 'pipe', 'pipe'], encoding: 'utf8' });
|
|
132
|
+
const line = out.split('\n').find(l => l.toLowerCase().startsWith('x-oauth-scopes:'));
|
|
133
|
+
return line ? line.split(':').slice(1).join(':').split(',').map(s => s.trim()) : [];
|
|
134
|
+
} catch (e) {
|
|
135
|
+
return [];
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const scopesBefore = getScopes();
|
|
140
|
+
if (scopesBefore.length > 0 && !scopesBefore.includes('project')) {
|
|
141
|
+
console.log(`${yellow}⚠️ Token missing 'project' scope — required for GitHub Projects board.${reset}`);
|
|
142
|
+
console.log(`${yellow} Refreshing token now (browser may open)...${reset}\n`);
|
|
143
|
+
try {
|
|
144
|
+
execSync('gh auth refresh -h github.com -s project', { stdio: 'inherit' });
|
|
145
|
+
} catch (e) {
|
|
146
|
+
console.log(`${red}❌ Token refresh failed. Run manually: gh auth refresh -h github.com -s project${reset}`);
|
|
147
|
+
rl.close();
|
|
148
|
+
process.exit(1);
|
|
149
|
+
}
|
|
150
|
+
const scopesAfter = getScopes();
|
|
151
|
+
if (scopesAfter.length > 0 && !scopesAfter.includes('project')) {
|
|
152
|
+
console.log(`\n${red}❌ 'project' scope still missing after refresh.${reset}`);
|
|
153
|
+
console.log(`${yellow} Active scopes: ${scopesAfter.join(', ')}${reset}`);
|
|
154
|
+
console.log(`${yellow} Note: gh uses OAuth tokens — visible at github.com/settings/applications, not /settings/tokens${reset}`);
|
|
155
|
+
console.log(`${yellow} Try manually: gh auth refresh -h github.com -s project${reset}`);
|
|
156
|
+
rl.close();
|
|
157
|
+
process.exit(1);
|
|
158
|
+
}
|
|
159
|
+
if (scopesAfter.length > 0) {
|
|
160
|
+
console.log(`\n${green}✅ Token refreshed. Active scopes: ${scopesAfter.join(', ')}${reset}\n`);
|
|
161
|
+
} else {
|
|
162
|
+
console.log(`\n${green}✅ Token refreshed with 'project' scope.${reset}\n`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
124
166
|
const agentAnswer = await askQuestion(i18n.ask_agent);
|
|
125
167
|
const agent = agentAnswer.trim().toLowerCase();
|
|
126
168
|
if (!['claude', 'cursor', 'copilot'].includes(agent)) {
|
|
@@ -190,14 +232,14 @@ async function runSetup() {
|
|
|
190
232
|
let ownerId, projectId, projectNumber;
|
|
191
233
|
try {
|
|
192
234
|
if (isOrg) {
|
|
193
|
-
const orgOutput = execFileSync('gh', ['api', 'graphql', '-f', 'query=query($login: String!) { organization(login: $login) { id } }', '-f', `login=${repoOwner}`, '--jq', '.data.organization.id']).toString().trim();
|
|
235
|
+
const orgOutput = execFileSync('gh', ['api', 'graphql', '-f', 'query=query($login: String!) { organization(login: $login) { id } }', '-f', `login=${repoOwner}`, '--jq', '.data.organization.id'], { stdio: ['ignore', 'pipe', 'pipe'] }).toString().trim();
|
|
194
236
|
ownerId = orgOutput;
|
|
195
237
|
} else {
|
|
196
|
-
const userOutput = execFileSync('gh', ['api', 'graphql', '-f', 'query={ viewer { id } }', '--jq', '.data.viewer.id']).toString().trim();
|
|
238
|
+
const userOutput = execFileSync('gh', ['api', 'graphql', '-f', 'query={ viewer { id } }', '--jq', '.data.viewer.id'], { stdio: ['ignore', 'pipe', 'pipe'] }).toString().trim();
|
|
197
239
|
ownerId = userOutput;
|
|
198
240
|
}
|
|
199
241
|
|
|
200
|
-
const projectCreateRaw = execFileSync('gh', ['api', 'graphql', '-f', 'query=mutation($owner: ID!, $title: String!) { createProjectV2(input: {ownerId: $owner, title: $title}) { projectV2 { id number } } }', '-f', `owner=${ownerId}`, '-f', `title=${boardName}`]).toString().trim();
|
|
242
|
+
const projectCreateRaw = execFileSync('gh', ['api', 'graphql', '-f', 'query=mutation($owner: ID!, $title: String!) { createProjectV2(input: {ownerId: $owner, title: $title}) { projectV2 { id number } } }', '-f', `owner=${ownerId}`, '-f', `title=${boardName}`], { stdio: ['ignore', 'pipe', 'pipe'] }).toString().trim();
|
|
201
243
|
const projectCreateResponse = JSON.parse(projectCreateRaw);
|
|
202
244
|
if (projectCreateResponse.errors) {
|
|
203
245
|
throw new Error(projectCreateResponse.errors.map(e => e.message).join('; '));
|
|
@@ -217,7 +259,14 @@ async function runSetup() {
|
|
|
217
259
|
}
|
|
218
260
|
|
|
219
261
|
} catch (err) {
|
|
220
|
-
|
|
262
|
+
const isScopeError = (err.message || '').includes("required scopes") || (err.stderr || '').toString().includes("required scopes");
|
|
263
|
+
if (isScopeError) {
|
|
264
|
+
console.log(` ${red}❌ Token missing 'project' scope.${reset}`);
|
|
265
|
+
console.log(` ${yellow}Fix: gh auth refresh -s project${reset}`);
|
|
266
|
+
console.log(` ${yellow}Then re-run: npx create-agentic-pdlc${reset}`);
|
|
267
|
+
} else {
|
|
268
|
+
console.log(` ${i18n.project_err}${err.message}`);
|
|
269
|
+
}
|
|
221
270
|
}
|
|
222
271
|
|
|
223
272
|
let statusFieldId;
|
|
@@ -278,6 +327,23 @@ async function runSetup() {
|
|
|
278
327
|
}
|
|
279
328
|
}
|
|
280
329
|
|
|
330
|
+
// Auto-provision PROJECT_PAT for personal repos
|
|
331
|
+
let patAutoSet = false;
|
|
332
|
+
if (projectId && !isOrg) {
|
|
333
|
+
try {
|
|
334
|
+
const tokenOut = execFileSync('gh', ['auth', 'token'], { stdio: ['ignore', 'pipe', 'pipe'], encoding: 'utf8' }).trim();
|
|
335
|
+
if (tokenOut) {
|
|
336
|
+
execFileSync('gh', ['secret', 'set', 'PROJECT_PAT', '--body', tokenOut, '--repo', repo], { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
337
|
+
patAutoSet = true;
|
|
338
|
+
console.log(`\n${green}✅ PROJECT_PAT secret set automatically (uses your gh OAuth token).${reset}`);
|
|
339
|
+
}
|
|
340
|
+
} catch (err) {
|
|
341
|
+
console.log(`\n${yellow}⚠️ Could not auto-set PROJECT_PAT. Agent will guide manual setup.${reset}`);
|
|
342
|
+
}
|
|
343
|
+
} else if (projectId && isOrg) {
|
|
344
|
+
console.log(`\n${yellow}ℹ️ Org repo detected — PROJECT_PAT will require manual setup for security.${reset}`);
|
|
345
|
+
}
|
|
346
|
+
|
|
281
347
|
console.log(`\n${yellow}${i18n.scaffolding}${reset}`);
|
|
282
348
|
|
|
283
349
|
// We copy the templates folder so the agent has the real text logic to replace and rename
|
|
@@ -311,7 +377,11 @@ async function runSetup() {
|
|
|
311
377
|
}
|
|
312
378
|
|
|
313
379
|
fs.writeFileSync(pdlcDest, pdlcContent);
|
|
314
|
-
|
|
380
|
+
if (projectId && statusFieldId && Object.keys(optionMap).length > 0) {
|
|
381
|
+
console.log(`${i18n.pdlc_prefilled}`);
|
|
382
|
+
} else {
|
|
383
|
+
console.log(`${yellow}⚠️ pdlc.md copied — Project IDs not filled (board creation failed). Re-run after fixing token.${reset}`);
|
|
384
|
+
}
|
|
315
385
|
}
|
|
316
386
|
|
|
317
387
|
// Pre-fill project-automation.yml with the same IDs so the agent doesn't need to map them
|
|
@@ -338,7 +408,16 @@ async function runSetup() {
|
|
|
338
408
|
// Write CLI context for the agent to consume in Setup Mode
|
|
339
409
|
try {
|
|
340
410
|
const cliContextPath = path.join(targetDir, '.agentic-pdlc', 'cli-context.json');
|
|
341
|
-
|
|
411
|
+
const boardUrl = projectNumber ? buildBoardUrl(repoOwner, projectNumber, isOrg) : null;
|
|
412
|
+
fs.writeFileSync(cliContextPath, JSON.stringify({
|
|
413
|
+
projectName,
|
|
414
|
+
repoOwner,
|
|
415
|
+
repoName,
|
|
416
|
+
projectNumber,
|
|
417
|
+
isOrg,
|
|
418
|
+
boardUrl,
|
|
419
|
+
patAutoSet
|
|
420
|
+
}, null, 2));
|
|
342
421
|
} catch (err) {
|
|
343
422
|
// Non-fatal — agent will ask for the values instead
|
|
344
423
|
}
|