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.
@@ -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`. Use these values directly and skip the corresponding questions.
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. **Set up the `PROJECT_PAT` secret (required for board automation):**
54
- The board automation workflows need a GitHub Personal Access Token (classic) with `project` scope. Without it, all board card movements will silently skip — no error, no cards moving.
55
- - Go to: **github.com/settings/tokens** → *Generate new token (classic)*
56
- - Select scopes: ✅ `repo` + ✅ `project`
57
- - Copy the token, then run:
58
- ```
59
- gh secret set PROJECT_PAT --body "<your-token>"
60
- ```
61
- Wait for the user to confirm the secret is set before continuing.
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 `projectNumber` from `.agentic-pdlc/cli-context.json` and show the user their board URL:
69
- `https://github.com/users/{repoOwner}/projects/{projectNumber}/views/1?layout=board`
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
- console.log(` ${i18n.project_err}${err.message}`);
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
- console.log(`${i18n.pdlc_prefilled}`);
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
- fs.writeFileSync(cliContextPath, JSON.stringify({ projectName, repoOwner, repoName, projectNumber }, null, 2));
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-agentic-pdlc",
3
- "version": "2.2.0",
3
+ "version": "2.2.1",
4
4
  "description": "Agentic PDLC Framework - Conversational setup for your AI coding assistants",
5
5
  "type": "commonjs",
6
6
  "bin": {