create-agentic-pdlc 2.3.0 → 3.0.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.
Files changed (64) hide show
  1. package/.agentic-pdlc/hooks/pdlc-stage-gate.sh +37 -10
  2. package/.agentic-pdlc/metrics/raw/2026-W22.jsonl +114 -0
  3. package/.claude/settings.json +18 -0
  4. package/.coderabbit.yaml +35 -0
  5. package/.github/ISSUE_TEMPLATE/bug.md +53 -0
  6. package/.github/ISSUE_TEMPLATE/feature.md +54 -0
  7. package/.github/ISSUE_TEMPLATE/task.md +33 -0
  8. package/.github/workflows/add-to-board.yml +1 -1
  9. package/.github/workflows/agent-trigger.yml +4 -4
  10. package/.github/workflows/ci.yml +1 -1
  11. package/.github/workflows/npm-publish.yml +2 -2
  12. package/.github/workflows/pdlc-health-check.yml +1 -1
  13. package/.github/workflows/pdlc-stage-gate.yml +2 -2
  14. package/.github/workflows/project-automation.yml +25 -40
  15. package/AGENTS.md +50 -8
  16. package/CLAUDE.md +3 -1
  17. package/README.md +33 -32
  18. package/SETUP.md +2 -1
  19. package/adapters/claude-code/skill.md +39 -14
  20. package/adapters/hooks/pdlc-stage-gate.sh +3 -8
  21. package/bin/cli.js +555 -194
  22. package/docs/pdlc.md +5 -5
  23. package/docs/superpowers/plans/2026-05-28-jules-label-pat-split.md +240 -0
  24. package/docs/superpowers/plans/2026-05-29-agentic-pulse-rework-taxonomy.md +474 -0
  25. package/docs/superpowers/plans/2026-05-29-qa-gate-enforcement.md +354 -0
  26. package/docs/superpowers/plans/2026-06-04-spec-format-issue-template.md +160 -0
  27. package/docs/superpowers/plans/2026-06-04-two-tier-installer.md +1056 -0
  28. package/docs/superpowers/specs/2026-05-29-agentic-pulse-rework-taxonomy-design.md +122 -0
  29. package/docs/superpowers/specs/2026-06-04-spec-format-issue-template-design.md +46 -0
  30. package/package.json +2 -2
  31. package/templates/.github/ISSUE_TEMPLATE/bug.md +53 -0
  32. package/templates/.github/ISSUE_TEMPLATE/feature.md +54 -0
  33. package/templates/.github/ISSUE_TEMPLATE/task.md +33 -0
  34. package/templates/.github/workflows/add-to-board.yml +4 -4
  35. package/templates/.github/workflows/agent-trigger.yml +22 -13
  36. package/{.agentic-pdlc/templates → templates}/.github/workflows/agentic-metrics.yml +150 -27
  37. package/templates/.github/workflows/ci.yml +1 -1
  38. package/templates/.github/workflows/pdlc-health-check.yml +1 -1
  39. package/templates/.github/workflows/pdlc-stage-gate.yml +2 -2
  40. package/templates/.github/workflows/project-automation.yml +71 -32
  41. package/templates/.github/workflows/qa-agent.yml +32 -18
  42. package/templates/.github/workflows/qa-gate.yml +51 -0
  43. package/templates/full/AGENTS.md +143 -0
  44. package/templates/full/CLAUDE.md +30 -0
  45. package/templates/{docs → full/docs}/pdlc.md +4 -4
  46. package/templates/lite/AGENTS.md +121 -0
  47. package/templates/lite/CLAUDE.md +44 -0
  48. package/tests/cli.test.js +32 -0
  49. package/.agentic-pdlc/templates/.github/CODEOWNERS +0 -5
  50. package/.agentic-pdlc/templates/.github/copilot-instructions.md +0 -12
  51. package/.agentic-pdlc/templates/.github/workflows/add-to-board.yml +0 -38
  52. package/.agentic-pdlc/templates/.github/workflows/agent-trigger.yml +0 -146
  53. package/.agentic-pdlc/templates/.github/workflows/auto-approve.yml +0 -16
  54. package/.agentic-pdlc/templates/.github/workflows/ci.yml +0 -54
  55. package/.agentic-pdlc/templates/.github/workflows/pdlc-health-check.yml +0 -121
  56. package/.agentic-pdlc/templates/.github/workflows/pdlc-stage-gate.yml +0 -51
  57. package/.agentic-pdlc/templates/.github/workflows/project-automation.yml +0 -274
  58. package/.agentic-pdlc/templates/.github/workflows/protect-workflows.yml +0 -21
  59. package/.agentic-pdlc/templates/.github/workflows/qa-agent.yml +0 -128
  60. package/.agentic-pdlc/templates/AGENTS.md +0 -104
  61. package/.agentic-pdlc/templates/docs/pdlc.md +0 -123
  62. package/.github/workflows/agentic-metrics.yml +0 -422
  63. package/.github/workflows/qa-agent.yml +0 -128
  64. package/templates/AGENTS.md +0 -115
@@ -0,0 +1,1056 @@
1
+ # Two-Tier Installer 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:** Refactor `bin/cli.js` so `npx create-agentic-pdlc` installs a minimal lite profile by default and `--agentic` opts into the full board machine.
6
+
7
+ **Architecture:** Extract `runSetup()` into three separate functions (`runLiteSetup`, `runFullSetup`, `runUpgradeToAgentic`) sharing helper functions for auth, hook install, branch protection, and template copying. Entry point routes by CLI arg.
8
+
9
+ **Tech Stack:** Node.js 18+, built-in `node:test` + `node:assert` for unit tests, `child_process.execFileSync`, `fs`, `path`.
10
+
11
+ ---
12
+
13
+ ## File Map
14
+
15
+ | File | Change |
16
+ |---|---|
17
+ | `bin/cli.js` | Main refactor — extract helpers, add three profile functions, update entry point |
18
+ | `tests/cli.test.js` | New — unit tests for pure helpers |
19
+ | `package.json` | Update test script to run `node:test` |
20
+
21
+ ---
22
+
23
+ ## Task 1: Create branch + add test infrastructure
24
+
25
+ **Files:**
26
+ - Modify: `package.json`
27
+ - Create: `tests/cli.test.js`
28
+
29
+ - [ ] **Step 1: Update package.json test script**
30
+
31
+ Open `package.json` and replace:
32
+ ```json
33
+ "test": "echo \"Error: no test specified\" && exit 1"
34
+ ```
35
+ with:
36
+ ```json
37
+ "test": "node --test tests/cli.test.js"
38
+ ```
39
+
40
+ - [ ] **Step 2: Create `tests/cli.test.js` with routing tests**
41
+
42
+ ```js
43
+ const { describe, it } = require('node:test');
44
+ const assert = require('node:assert/strict');
45
+
46
+ // We import helpers from cli.js once they are extracted.
47
+ // For now, define the expected pure functions here as contracts.
48
+
49
+ function resolveMode(args) {
50
+ if (args.includes('--update')) return 'update';
51
+ if (args.includes('--upgrade-to-agentic')) return 'upgrade';
52
+ if (args.includes('--agentic')) return 'full';
53
+ return 'lite';
54
+ }
55
+
56
+ describe('resolveMode', () => {
57
+ it('returns lite when no flags', () => {
58
+ assert.equal(resolveMode([]), 'lite');
59
+ });
60
+ it('returns full for --agentic', () => {
61
+ assert.equal(resolveMode(['--agentic']), 'full');
62
+ });
63
+ it('returns update for --update', () => {
64
+ assert.equal(resolveMode(['--update']), 'update');
65
+ });
66
+ it('returns upgrade for --upgrade-to-agentic', () => {
67
+ assert.equal(resolveMode(['--upgrade-to-agentic']), 'upgrade');
68
+ });
69
+ it('--update takes precedence over --agentic', () => {
70
+ assert.equal(resolveMode(['--update', '--agentic']), 'update');
71
+ });
72
+ });
73
+
74
+ describe('buildFullClaudeContent', () => {
75
+ it('concatenates lite and full with a newline separator', () => {
76
+ const lite = '# Lite\ncontent';
77
+ const full = '## Extra\nmore';
78
+ const result = lite + '\n' + full;
79
+ assert.ok(result.startsWith('# Lite'));
80
+ assert.ok(result.includes('## Extra'));
81
+ });
82
+ });
83
+ ```
84
+
85
+ - [ ] **Step 3: Run tests — expect PASS (pure functions defined inline)**
86
+
87
+ ```bash
88
+ npm test
89
+ ```
90
+
91
+ Expected output: `✔ resolveMode` and `✔ buildFullClaudeContent` tests all pass.
92
+
93
+ - [ ] **Step 4: Commit**
94
+
95
+ ```bash
96
+ git add package.json tests/cli.test.js
97
+ git commit -m "test: add test infra and routing contract tests"
98
+ ```
99
+
100
+ ---
101
+
102
+ ## Task 2: Extract `resolveMode` + update entry point in cli.js
103
+
104
+ **Files:**
105
+ - Modify: `bin/cli.js:700-708`
106
+
107
+ - [ ] **Step 1: Read the current entry point block** (lines 700–708 of `bin/cli.js`)
108
+
109
+ Current code at the bottom of `bin/cli.js`:
110
+ ```js
111
+ const args = process.argv.slice(2);
112
+ if (args.includes('--update')) {
113
+ runUpdate().catch(err => { console.error(err.message); rl.close(); process.exit(1); });
114
+ } else {
115
+ runSetup().catch(err => { console.error(err.message); rl.close(); process.exit(1); });
116
+ }
117
+ ```
118
+
119
+ - [ ] **Step 2: Replace entry point with routed version**
120
+
121
+ Replace the block above with:
122
+ ```js
123
+ function resolveMode(args) {
124
+ if (args.includes('--update')) return 'update';
125
+ if (args.includes('--upgrade-to-agentic')) return 'upgrade';
126
+ if (args.includes('--agentic')) return 'full';
127
+ return 'lite';
128
+ }
129
+
130
+ const args = process.argv.slice(2);
131
+ const mode = resolveMode(args);
132
+
133
+ const handler =
134
+ mode === 'update' ? runUpdate :
135
+ mode === 'upgrade' ? runUpgradeToAgentic :
136
+ mode === 'full' ? runFullSetup :
137
+ runLiteSetup;
138
+
139
+ handler().catch(err => { console.error(err.message); rl.close(); process.exit(1); });
140
+ ```
141
+
142
+ Note: `runUpgradeToAgentic`, `runFullSetup`, and `runLiteSetup` will be added in later tasks. The file will not run cleanly until Task 6.
143
+
144
+ - [ ] **Step 3: Commit**
145
+
146
+ ```bash
147
+ git add bin/cli.js
148
+ git commit -m "refactor(cli): add resolveMode and routed entry point"
149
+ ```
150
+
151
+ ---
152
+
153
+ ## Task 3: Extract shared helper functions
154
+
155
+ **Files:**
156
+ - Modify: `bin/cli.js` — add helpers after `printSetupDone()`, before `runSetup()`
157
+
158
+ These helpers extract repeated logic from the current `runSetup()`. Add them as named functions between `printSetupDone()` (line ~119) and the start of `runSetup()` (line ~121).
159
+
160
+ - [ ] **Step 1: Add `checkGhAuth()` helper**
161
+
162
+ ```js
163
+ async function checkGhAuth() {
164
+ console.log(`${yellow}${i18n.checking_gh}${reset}`);
165
+ try {
166
+ execSync('gh auth status', { stdio: 'ignore' });
167
+ console.log(`${green}${i18n.gh_ok}${reset}\n`);
168
+ } catch (error) {
169
+ console.error(`${red}${i18n.gh_error}${reset}`);
170
+ console.error(`${i18n.gh_install}`);
171
+ rl.close();
172
+ process.exit(1);
173
+ }
174
+ }
175
+ ```
176
+
177
+ - [ ] **Step 2: Add `checkAndRefreshProjectScope()` helper**
178
+
179
+ ```js
180
+ function getScopes() {
181
+ try {
182
+ const out = execFileSync('gh', ['api', 'user', '-i'], { stdio: ['ignore', 'pipe', 'pipe'], encoding: 'utf8' });
183
+ const line = out.split('\n').find(l => l.toLowerCase().startsWith('x-oauth-scopes:'));
184
+ return line ? line.split(':').slice(1).join(':').split(',').map(s => s.trim()) : [];
185
+ } catch (e) {
186
+ return [];
187
+ }
188
+ }
189
+
190
+ async function checkAndRefreshProjectScope() {
191
+ const scopesBefore = getScopes();
192
+ if (scopesBefore.length === 0 || scopesBefore.includes('project')) return;
193
+
194
+ console.log(`${yellow}⚠️ Token missing 'project' scope — required for GitHub Projects board.${reset}`);
195
+ console.log(`${yellow} Refreshing token now (browser may open)...${reset}\n`);
196
+ try {
197
+ execSync('gh auth refresh -h github.com -s project', { stdio: 'inherit' });
198
+ } catch (e) {
199
+ console.log(`${red}❌ Token refresh failed. Run manually: gh auth refresh -h github.com -s project${reset}`);
200
+ rl.close();
201
+ process.exit(1);
202
+ }
203
+ const scopesAfter = getScopes();
204
+ if (scopesAfter.length > 0 && !scopesAfter.includes('project')) {
205
+ console.log(`\n${red}❌ 'project' scope still missing after refresh.${reset}`);
206
+ console.log(`${yellow} Active scopes: ${scopesAfter.join(', ')}${reset}`);
207
+ console.log(`${yellow} Try manually: gh auth refresh -h github.com -s project${reset}`);
208
+ rl.close();
209
+ process.exit(1);
210
+ }
211
+ if (scopesAfter.length > 0) {
212
+ console.log(`\n${green}✅ Token refreshed. Active scopes: ${scopesAfter.join(', ')}${reset}\n`);
213
+ } else {
214
+ console.log(`\n${green}✅ Token refreshed with 'project' scope.${reset}\n`);
215
+ }
216
+ }
217
+ ```
218
+
219
+ Note: `getScopes()` and its `try/catch` block already exist inside `runSetup()`. This step moves them to module scope and wraps them in `checkAndRefreshProjectScope()`.
220
+
221
+ - [ ] **Step 3: Add `installHook(sourceDir, targetDir)` helper**
222
+
223
+ ```js
224
+ function installHook(sourceDir, targetDir) {
225
+ const hookSrc = path.join(sourceDir, 'adapters', 'hooks', 'pdlc-stage-gate.sh');
226
+ const hookDir = path.join(targetDir, '.agentic-pdlc', 'hooks');
227
+ const hookDest = path.join(hookDir, 'pdlc-stage-gate.sh');
228
+ if (!fs.existsSync(hookSrc)) return;
229
+
230
+ fs.mkdirSync(hookDir, { recursive: true });
231
+ fs.copyFileSync(hookSrc, hookDest);
232
+ fs.chmodSync(hookDest, '755');
233
+
234
+ const settingsDir = path.join(targetDir, '.claude');
235
+ const settingsPath = path.join(settingsDir, 'settings.json');
236
+ if (!fs.existsSync(settingsPath)) {
237
+ fs.mkdirSync(settingsDir, { recursive: true });
238
+ fs.writeFileSync(settingsPath, JSON.stringify({
239
+ hooks: {
240
+ PreToolUse: [{
241
+ matcher: 'Bash',
242
+ hooks: [{ type: 'command', command: 'bash .agentic-pdlc/hooks/pdlc-stage-gate.sh' }]
243
+ }]
244
+ }
245
+ }, null, 2) + '\n');
246
+ }
247
+ }
248
+ ```
249
+
250
+ - [ ] **Step 4: Add `setBranchProtection(repo, requiredChecks)` helper**
251
+
252
+ ```js
253
+ async function setBranchProtection(repo, requiredChecks) {
254
+ console.log(`\n${cyan}${i18n.configuring_protection}${reset}`);
255
+ try {
256
+ const defaultBranch = execFileSync(
257
+ 'gh', ['api', `repos/${repo}`, '--jq', '.default_branch'],
258
+ { stdio: ['ignore', 'pipe', 'ignore'], encoding: 'utf8' }
259
+ ).trim() || 'main';
260
+
261
+ const protectionPayload = JSON.stringify({
262
+ required_status_checks: { strict: false, contexts: requiredChecks },
263
+ enforce_admins: false,
264
+ required_pull_request_reviews: null,
265
+ restrictions: null
266
+ });
267
+
268
+ execFileSync(
269
+ 'gh',
270
+ ['api', `repos/${repo}/branches/${defaultBranch}/protection`, '--method', 'PUT', '--input', '-'],
271
+ { input: protectionPayload, stdio: ['pipe', 'ignore', 'pipe'] }
272
+ );
273
+ console.log(` ${green}${i18n.protection_ok}${reset}`);
274
+ } catch (_) {
275
+ console.log(` ${yellow}${i18n.protection_warn}${reset}`);
276
+ }
277
+ }
278
+ ```
279
+
280
+ - [ ] **Step 5: Add `copyAdapterFiles(agent, sourceDir, targetDir)` helper**
281
+
282
+ ```js
283
+ function copyAdapterFiles(agent, sourceDir, targetDir) {
284
+ const claudeSetupSrc = path.join(sourceDir, 'adapters', 'claude-code', 'skill.md');
285
+ const cursorSetupSrc = path.join(sourceDir, 'adapters', 'cursor', 'rules.md');
286
+
287
+ if (agent === 'cursor') {
288
+ if (fs.existsSync(cursorSetupSrc)) {
289
+ fs.copyFileSync(cursorSetupSrc, path.join(targetDir, '.cursorrules'));
290
+ console.log(`${i18n.cursor_rules_written}`);
291
+ }
292
+ }
293
+ if (fs.existsSync(claudeSetupSrc)) {
294
+ fs.copyFileSync(claudeSetupSrc, path.join(targetDir, '.agentic-setup.md'));
295
+ console.log(`${i18n.setup_written}`);
296
+ printSetupDone();
297
+ } else {
298
+ console.error(`${i18n.missing_claude}${claudeSetupSrc}`);
299
+ }
300
+ }
301
+ ```
302
+
303
+ - [ ] **Step 6: Add `scaffoldLiteTemplates(sourceDir, targetDir)` helper**
304
+
305
+ ```js
306
+ function scaffoldLiteTemplates(sourceDir, targetDir) {
307
+ const destTemplates = path.join(targetDir, '.agentic-pdlc', 'templates');
308
+ fs.mkdirSync(destTemplates, { recursive: true });
309
+
310
+ // CLAUDE.md — lite version
311
+ const liteClaudeSrc = path.join(sourceDir, 'templates', 'lite', 'CLAUDE.md');
312
+ if (fs.existsSync(liteClaudeSrc)) {
313
+ fs.copyFileSync(liteClaudeSrc, path.join(destTemplates, 'CLAUDE.md'));
314
+ }
315
+
316
+ // AGENTS.md — lite version
317
+ const liteAgentsSrc = path.join(sourceDir, 'templates', 'lite', 'AGENTS.md');
318
+ if (fs.existsSync(liteAgentsSrc)) {
319
+ fs.copyFileSync(liteAgentsSrc, path.join(destTemplates, 'AGENTS.md'));
320
+ }
321
+
322
+ // Issue templates — shared between lite and full
323
+ const issueTemplateSrc = path.join(sourceDir, 'templates', '.github', 'ISSUE_TEMPLATE');
324
+ const issueTemplateDest = path.join(destTemplates, '.github', 'ISSUE_TEMPLATE');
325
+ if (fs.existsSync(issueTemplateSrc)) {
326
+ copyDirSync(issueTemplateSrc, issueTemplateDest);
327
+ }
328
+ }
329
+ ```
330
+
331
+ - [ ] **Step 7: Add `scaffoldFullTemplates(sourceDir, targetDir, projectId, statusFieldId, optionMap, repoOwner, repoName)` helper**
332
+
333
+ ```js
334
+ function scaffoldFullTemplates(sourceDir, targetDir, projectId, statusFieldId, optionMap, repoOwner, repoName) {
335
+ const destTemplates = path.join(targetDir, '.agentic-pdlc', 'templates');
336
+ fs.mkdirSync(destTemplates, { recursive: true });
337
+
338
+ // CLAUDE.md — concatenate lite + full addon
339
+ const liteClaudeSrc = path.join(sourceDir, 'templates', 'lite', 'CLAUDE.md');
340
+ const fullClaudeSrc = path.join(sourceDir, 'templates', 'full', 'CLAUDE.md');
341
+ if (fs.existsSync(liteClaudeSrc) && fs.existsSync(fullClaudeSrc)) {
342
+ const combined = fs.readFileSync(liteClaudeSrc, 'utf8') + '\n' + fs.readFileSync(fullClaudeSrc, 'utf8');
343
+ fs.writeFileSync(path.join(destTemplates, 'CLAUDE.md'), combined);
344
+ } else if (fs.existsSync(liteClaudeSrc)) {
345
+ fs.copyFileSync(liteClaudeSrc, path.join(destTemplates, 'CLAUDE.md'));
346
+ }
347
+
348
+ // AGENTS.md — full version
349
+ const fullAgentsSrc = path.join(sourceDir, 'templates', 'full', 'AGENTS.md');
350
+ if (fs.existsSync(fullAgentsSrc)) {
351
+ fs.copyFileSync(fullAgentsSrc, path.join(destTemplates, 'AGENTS.md'));
352
+ }
353
+
354
+ // All of templates/.github/ (issue templates + workflows)
355
+ const githubSrc = path.join(sourceDir, 'templates', '.github');
356
+ const githubDest = path.join(destTemplates, '.github');
357
+ if (fs.existsSync(githubSrc)) {
358
+ copyDirSync(githubSrc, githubDest);
359
+ }
360
+
361
+ // docs/pdlc.md — substitute board IDs
362
+ const pdlcSrc = path.join(sourceDir, 'templates', 'full', 'docs', 'pdlc.md');
363
+ const pdlcDest = path.join(destTemplates, 'docs', 'pdlc.md');
364
+ if (fs.existsSync(pdlcSrc)) {
365
+ fs.mkdirSync(path.join(destTemplates, 'docs'), { recursive: true });
366
+ let pdlcContent = fs.readFileSync(pdlcSrc, 'utf8');
367
+ if (projectId) pdlcContent = pdlcContent.replace(/\{\{PROJECT_ID\}\}/g, () => projectId);
368
+ if (statusFieldId) pdlcContent = pdlcContent.replace(/\{\{STATUS_FIELD_ID\}\}/g, () => statusFieldId);
369
+ pdlcContent = pdlcContent.replace(/\{\{REPO_OWNER\}\}/g, () => repoOwner);
370
+ pdlcContent = pdlcContent.replace(/\{\{REPO_NAME\}\}/g, () => repoName);
371
+ if (Object.keys(optionMap).length > 0) {
372
+ pdlcContent = pdlcContent.replace(/\{\{ID_IDEA\}\}/g, () => optionMap['💡 Idea - No move to Exploration directly'] || 'MISSING_ID');
373
+ pdlcContent = pdlcContent.replace(/\{\{ID_EXPLORATION\}\}/g, () => optionMap['🔍 Exploration'] || 'MISSING_ID');
374
+ pdlcContent = pdlcContent.replace(/\{\{ID_BRAINSTORMING\}\}/g, () => optionMap['🧠 Brainstorming'] || 'MISSING_ID');
375
+ pdlcContent = pdlcContent.replace(/\{\{ID_DETAIL\}\}/g, () => optionMap['📐 Detail Solution'] || 'MISSING_ID');
376
+ pdlcContent = pdlcContent.replace(/\{\{ID_APPROVAL\}\}/g, () => optionMap['✅ Approval'] || 'MISSING_ID');
377
+ pdlcContent = pdlcContent.replace(/\{\{ID_DEVELOPMENT\}\}/g, () => optionMap['⚙️ Development'] || 'MISSING_ID');
378
+ pdlcContent = pdlcContent.replace(/\{\{ID_TESTING\}\}/g, () => optionMap['🧪 Testing'] || 'MISSING_ID');
379
+ pdlcContent = pdlcContent.replace(/\{\{ID_CODE_REVIEW_PR\}\}/g, () => optionMap['👁 Code Review / PR'] || 'MISSING_ID');
380
+ pdlcContent = pdlcContent.replace(/\{\{ID_READY_FOR_PRODUCTION\}\}/g,() => optionMap['🚀 Ready for Production']|| 'MISSING_ID');
381
+ }
382
+ fs.writeFileSync(pdlcDest, pdlcContent);
383
+ if (projectId && statusFieldId && Object.keys(optionMap).length > 0) {
384
+ console.log(`${i18n.pdlc_prefilled}`);
385
+ } else {
386
+ console.log(`${yellow}⚠️ pdlc.md copied — Project IDs not filled (board creation failed). Re-run after fixing token.${reset}`);
387
+ }
388
+ }
389
+
390
+ // project-automation.yml — substitute IDs
391
+ const paPath = path.join(destTemplates, '.github', 'workflows', 'project-automation.yml');
392
+ if (fs.existsSync(paPath) && Object.keys(optionMap).length > 0) {
393
+ let wfContent = fs.readFileSync(paPath, 'utf8');
394
+ if (projectId) wfContent = wfContent.replace(/\{\{PROJECT_ID\}\}/g, () => projectId);
395
+ if (statusFieldId) wfContent = wfContent.replace(/\{\{STATUS_FIELD_ID\}\}/g, () => statusFieldId);
396
+ wfContent = wfContent.replace(/\{\{ID_IDEA\}\}/g, () => optionMap['💡 Idea - No move to Exploration directly'] || 'MISSING_ID');
397
+ wfContent = wfContent.replace(/\{\{ID_EXPLORATION\}\}/g, () => optionMap['🔍 Exploration'] || 'MISSING_ID');
398
+ wfContent = wfContent.replace(/\{\{ID_BRAINSTORMING\}\}/g, () => optionMap['🧠 Brainstorming'] || 'MISSING_ID');
399
+ wfContent = wfContent.replace(/\{\{ID_DETAILING\}\}/g, () => optionMap['📐 Detail Solution'] || 'MISSING_ID');
400
+ wfContent = wfContent.replace(/\{\{ID_APPROVAL\}\}/g, () => optionMap['✅ Approval'] || 'MISSING_ID');
401
+ wfContent = wfContent.replace(/\{\{ID_DEVELOPMENT\}\}/g, () => optionMap['⚙️ Development'] || 'MISSING_ID');
402
+ wfContent = wfContent.replace(/\{\{ID_TESTING\}\}/g, () => optionMap['🧪 Testing'] || 'MISSING_ID');
403
+ wfContent = wfContent.replace(/\{\{ID_CODE_REVIEW_PR\}\}/g, () => optionMap['👁 Code Review / PR'] || 'MISSING_ID');
404
+ wfContent = wfContent.replace(/\{\{ID_PRODUCTION\}\}/g, () => optionMap['🚀 Ready for Production']|| 'MISSING_ID');
405
+ fs.writeFileSync(paPath, wfContent);
406
+ }
407
+
408
+ console.log(`${i18n.templates_copied}`);
409
+ }
410
+ ```
411
+
412
+ - [ ] **Step 8: Add `writeCliContext(targetDir, profile, data)` helper**
413
+
414
+ ```js
415
+ function writeCliContext(targetDir, profile, data) {
416
+ try {
417
+ const contextPath = path.join(targetDir, '.agentic-pdlc', 'cli-context.json');
418
+ fs.mkdirSync(path.join(targetDir, '.agentic-pdlc'), { recursive: true });
419
+ fs.writeFileSync(contextPath, JSON.stringify({ profile, ...data }, null, 2));
420
+ } catch (_) {
421
+ // Non-fatal — agent will ask for the values instead
422
+ }
423
+ }
424
+ ```
425
+
426
+ - [ ] **Step 9: Remove now-duplicated helpers from `runSetup()` body**
427
+
428
+ In `runSetup()`, remove or comment out:
429
+ - The `getScopes()` inner function (moved to module scope)
430
+ - The `scopesBefore` / `scopesAfter` token refresh block (now in `checkAndRefreshProjectScope()`)
431
+ - The hook install block (lines ~457–478) — now in `installHook()`
432
+ - The branch protection block (lines ~362–377) — now in `setBranchProtection()`
433
+ - The adapter copy block (lines ~481–517) — now in `copyAdapterFiles()`
434
+
435
+ Do NOT refactor `runSetup()` further yet — that happens in Task 5.
436
+
437
+ - [ ] **Step 10: Commit**
438
+
439
+ ```bash
440
+ git add bin/cli.js
441
+ git commit -m "refactor(cli): extract shared helper functions"
442
+ ```
443
+
444
+ ---
445
+
446
+ ## Task 4: Implement `runLiteSetup()`
447
+
448
+ **Files:**
449
+ - Modify: `bin/cli.js` — add `runLiteSetup()` after `runSetup()`
450
+
451
+ - [ ] **Step 1: Add `runLiteSetup()` to `bin/cli.js`**
452
+
453
+ Insert the following function after the closing brace of `runSetup()` (before `runUpdate()`):
454
+
455
+ ```js
456
+ async function runLiteSetup() {
457
+ await checkGhAuth();
458
+
459
+ const agentAnswer = await askQuestion(i18n.ask_agent);
460
+ const agent = agentAnswer.trim().toLowerCase();
461
+ if (!['claude', 'cursor', 'copilot'].includes(agent)) {
462
+ console.log(t(
463
+ `ℹ️ Generating Universal Setup for '${agent}' (Compatible with any Markdown-reading agent).`,
464
+ `ℹ️ Gerando Setup Universal para '${agent}' (Compatível com qualquer agente que leia Markdown).`,
465
+ `ℹ️ Generando Setup Universal para '${agent}' (Compatible con cualquier agente que lea Markdown).`
466
+ ));
467
+ }
468
+
469
+ let repoOwner, repoName, repo;
470
+ while (true) {
471
+ let repoUrl = (await askQuestion(i18n.ask_repo)).trim();
472
+ if (repoUrl.endsWith('/')) repoUrl = repoUrl.slice(0, -1);
473
+ if (repoUrl.endsWith('.git')) repoUrl = repoUrl.slice(0, -4);
474
+ const repoParts = repoUrl.split('/');
475
+ if (repoParts.length >= 2) {
476
+ repoOwner = repoParts[repoParts.length - 2];
477
+ repoName = repoParts[repoParts.length - 1];
478
+ repo = `${repoOwner}/${repoName}`;
479
+ break;
480
+ }
481
+ console.log(`${red}${i18n.invalid_repo}${reset}`);
482
+ }
483
+
484
+ console.log(`\n${yellow}${i18n.starting_setup}${reset}`);
485
+
486
+ installHook(sourceDir, targetDir);
487
+
488
+ console.log(`\n${yellow}${i18n.scaffolding}${reset}`);
489
+ scaffoldLiteTemplates(sourceDir, targetDir);
490
+ console.log(`${i18n.templates_copied}`);
491
+
492
+ await setBranchProtection(repo, ['PDLC Stage Gate']);
493
+
494
+ writeCliContext(targetDir, 'lite', { repoOwner, repoName, projectNumber: null, isOrg: false, boardUrl: null, patAutoSet: false });
495
+
496
+ copyAdapterFiles(agent, sourceDir, targetDir);
497
+
498
+ rl.close();
499
+ }
500
+ ```
501
+
502
+ - [ ] **Step 2: Verify file runs without syntax errors**
503
+
504
+ ```bash
505
+ node --check bin/cli.js
506
+ ```
507
+
508
+ Expected: no output (no errors).
509
+
510
+ - [ ] **Step 3: Commit**
511
+
512
+ ```bash
513
+ git add bin/cli.js
514
+ git commit -m "feat(cli): implement runLiteSetup — lite profile"
515
+ ```
516
+
517
+ ---
518
+
519
+ ## Task 5: Refactor `runSetup()` into `runFullSetup()`
520
+
521
+ **Files:**
522
+ - Modify: `bin/cli.js` — rename and update `runSetup()`
523
+
524
+ The existing `runSetup()` already works correctly. This task renames it to `runFullSetup()`, replaces inline duplicated code with helper calls, and updates template copying to use `scaffoldFullTemplates()`.
525
+
526
+ - [ ] **Step 1: Rename `runSetup` → `runFullSetup`**
527
+
528
+ Find and replace (single occurrence — the function declaration):
529
+ ```js
530
+ async function runSetup() {
531
+ ```
532
+
533
+ ```js
534
+ async function runFullSetup() {
535
+ ```
536
+
537
+ - [ ] **Step 2: Replace inline hook install block with helper call**
538
+
539
+ Find the block (approximately lines 457–478 in original):
540
+ ```js
541
+ // Install PDLC stage gate hook (all agents)
542
+ const hookSrc = path.join(sourceDir, 'adapters', 'hooks', 'pdlc-stage-gate.sh');
543
+ const hookDir = path.join(targetDir, '.agentic-pdlc', 'hooks');
544
+ const hookDest = path.join(hookDir, 'pdlc-stage-gate.sh');
545
+ if (fs.existsSync(hookSrc)) {
546
+ fs.mkdirSync(hookDir, { recursive: true });
547
+ fs.copyFileSync(hookSrc, hookDest);
548
+ fs.chmodSync(hookDest, '755');
549
+ }
550
+ const claudeSettingsDir = path.join(targetDir, '.claude');
551
+ const claudeSettingsPath = path.join(claudeSettingsDir, 'settings.json');
552
+ if (!fs.existsSync(claudeSettingsPath)) {
553
+ fs.mkdirSync(claudeSettingsDir, { recursive: true });
554
+ fs.writeFileSync(claudeSettingsPath, JSON.stringify({
555
+ hooks: {
556
+ PreToolUse: [{
557
+ matcher: 'Bash',
558
+ hooks: [{ type: 'command', command: 'bash .agentic-pdlc/hooks/pdlc-stage-gate.sh' }]
559
+ }]
560
+ }
561
+ }, null, 2) + '\n');
562
+ }
563
+ ```
564
+
565
+ Replace with:
566
+ ```js
567
+ installHook(sourceDir, targetDir);
568
+ ```
569
+
570
+ - [ ] **Step 3: Replace inline branch protection block with helper call**
571
+
572
+ Find the block:
573
+ ```js
574
+ // Branch protection — require PDLC Stage Gate + QA Gate on default branch
575
+ console.log(`\n${cyan}${i18n.configuring_protection}${reset}`);
576
+ try {
577
+ const defaultBranch = execFileSync('gh', ['api', `repos/${repo}`, '--jq', '.default_branch'],
578
+ { stdio: ['ignore', 'pipe', 'ignore'], encoding: 'utf8' }).trim() || 'main';
579
+ const protectionPayload = JSON.stringify({
580
+ required_status_checks: { strict: false, contexts: ['PDLC Stage Gate', 'QA Gate'] },
581
+ enforce_admins: false,
582
+ required_pull_request_reviews: null,
583
+ restrictions: null
584
+ });
585
+ execFileSync('gh', ['api', `repos/${repo}/branches/${defaultBranch}/protection`, '--method', 'PUT', '--input', '-'],
586
+ { input: protectionPayload, stdio: ['pipe', 'ignore', 'pipe'] });
587
+ console.log(` ${green}${i18n.protection_ok}${reset}`);
588
+ } catch (_) {
589
+ console.log(` ${yellow}${i18n.protection_warn}${reset}`);
590
+ }
591
+ ```
592
+
593
+ Replace with:
594
+ ```js
595
+ await setBranchProtection(repo, ['PDLC Stage Gate', 'QA Gate']);
596
+ ```
597
+
598
+ - [ ] **Step 4: Replace inline template copying with `scaffoldFullTemplates()` call**
599
+
600
+ Find the block that starts with:
601
+ ```js
602
+ console.log(`\n${yellow}${i18n.scaffolding}${reset}`);
603
+
604
+ // We copy the templates folder so the agent has the real text logic to replace and rename
605
+ const sourceTemplates = path.join(sourceDir, 'templates');
606
+ const targetTemplates = path.join(targetDir, '.agentic-pdlc', 'templates');
607
+ ```
608
+ and ends just before the `// Write CLI context` block.
609
+
610
+ Replace the entire scaffolding block with:
611
+ ```js
612
+ console.log(`\n${yellow}${i18n.scaffolding}${reset}`);
613
+ scaffoldFullTemplates(sourceDir, targetDir, projectId, statusFieldId, optionMap, repoOwner, repoName);
614
+ ```
615
+
616
+ - [ ] **Step 5: Replace inline adapter copy block with helper call**
617
+
618
+ Find the block that starts with:
619
+ ```js
620
+ // Handle the specific setup instructions target
621
+ const claudeSetupSrc = path.join(sourceDir, 'adapters', 'claude-code', 'skill.md');
622
+ const cursorSetupSrc = path.join(sourceDir, 'adapters', 'cursor', 'rules.md');
623
+
624
+ if (agent === 'claude') {
625
+ ```
626
+ and continues to the end of `runFullSetup()` (the `rl.close()` call just before).
627
+
628
+ Replace with:
629
+ ```js
630
+ copyAdapterFiles(agent, sourceDir, targetDir);
631
+
632
+ rl.close();
633
+ ```
634
+
635
+ - [ ] **Step 6: Update `writeCliContext` call at the end of the existing block**
636
+
637
+ Find:
638
+ ```js
639
+ fs.writeFileSync(cliContextPath, JSON.stringify({
640
+ projectName,
641
+ repoOwner,
642
+ repoName,
643
+ projectNumber,
644
+ isOrg,
645
+ boardUrl,
646
+ patAutoSet
647
+ }, null, 2));
648
+ ```
649
+
650
+ Replace with:
651
+ ```js
652
+ writeCliContext(targetDir, 'full', {
653
+ projectName,
654
+ repoOwner,
655
+ repoName,
656
+ projectNumber,
657
+ isOrg,
658
+ boardUrl,
659
+ patAutoSet
660
+ });
661
+ ```
662
+
663
+ Also remove the surrounding `try/catch` block around the old `fs.writeFileSync` (the `writeCliContext` helper handles that internally).
664
+
665
+ - [ ] **Step 7: Remove the duplicate `getScopes()` inner function and inline token-refresh block**
666
+
667
+ Inside `runFullSetup()`, remove the inner `getScopes()` definition and the `scopesBefore`/`scopesAfter` block. Replace them with a single call:
668
+
669
+ ```js
670
+ await checkAndRefreshProjectScope();
671
+ ```
672
+
673
+ Place this call immediately after `await checkGhAuth();` and before the `agentAnswer` prompt.
674
+
675
+ - [ ] **Step 8: Syntax check**
676
+
677
+ ```bash
678
+ node --check bin/cli.js
679
+ ```
680
+
681
+ Expected: no output.
682
+
683
+ - [ ] **Step 9: Commit**
684
+
685
+ ```bash
686
+ git add bin/cli.js
687
+ git commit -m "refactor(cli): runSetup → runFullSetup, replace inline blocks with helpers"
688
+ ```
689
+
690
+ ---
691
+
692
+ ## Task 6: Implement `runUpgradeToAgentic()`
693
+
694
+ **Files:**
695
+ - Modify: `bin/cli.js` — add `runUpgradeToAgentic()` after `runLiteSetup()`
696
+
697
+ - [ ] **Step 1: Add `runUpgradeToAgentic()` to `bin/cli.js`**
698
+
699
+ Insert after `runLiteSetup()` and before `runUpdate()`:
700
+
701
+ ```js
702
+ async function runUpgradeToAgentic() {
703
+ const contextPath = path.join(targetDir, '.agentic-pdlc', 'cli-context.json');
704
+ if (!fs.existsSync(contextPath)) {
705
+ console.error(`\n${red}${i18n.update_no_context}${reset}\n`);
706
+ rl.close();
707
+ process.exit(1);
708
+ }
709
+
710
+ const ctx = JSON.parse(fs.readFileSync(contextPath, 'utf8'));
711
+ if (ctx.profile === 'full') {
712
+ console.log(`\n${green}✅ Already running full profile. Nothing to upgrade.${reset}\n`);
713
+ rl.close();
714
+ return;
715
+ }
716
+
717
+ await checkGhAuth();
718
+ await checkAndRefreshProjectScope();
719
+
720
+ const { repoOwner, repoName } = ctx;
721
+ const repo = `${repoOwner}/${repoName}`;
722
+
723
+ const askProjectName = t(
724
+ `What is the project name for the board? (default: ${repoName.toUpperCase()}): `,
725
+ `Qual o nome do projeto em que o board será configurado? (padrão: ${repoName.toUpperCase()}): `,
726
+ `¿Cuál es el nombre del proyecto en el que se configurará el board? (por defecto: ${repoName.toUpperCase()}): `
727
+ );
728
+ const projectNameAnswer = await askQuestion(askProjectName);
729
+ const projectName = projectNameAnswer.trim() ? projectNameAnswer.trim().toUpperCase() : repoName.toUpperCase();
730
+ const boardName = `BOARD - ${projectName}`;
731
+
732
+ let isOrg = ctx.isOrg || false;
733
+ try {
734
+ const ownerType = execFileSync('gh', ['api', `repos/${repo}`, '--jq', '.owner.type'],
735
+ { stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim();
736
+ isOrg = ownerType === 'Organization';
737
+ } catch (_) {}
738
+
739
+ console.log(`\n${yellow}${i18n.starting_setup}${reset}`);
740
+
741
+ // Labels
742
+ const labels = [
743
+ { name: 'stage:exploration', color: '9b59b6', description: 'Issue is being evaluated' },
744
+ { name: 'stage:brainstorming', color: 'e84393', description: 'Proposed approaches awaiting PM gate' },
745
+ { name: 'stage:detailing', color: '3498db', description: 'Technical spec is being written' },
746
+ { name: 'stage:development', color: 'e67e22', description: 'Agent is implementing the spec' },
747
+ { name: 'stage:testing', color: '8e44ad', description: 'Agent is testing the implementation' },
748
+ { name: 'spec:approved', color: '0e8a16', description: 'Spec approved — agent can implement' },
749
+ { name: 'pr:in-review', color: 'e4e669', description: 'PR awaiting code review' },
750
+ { name: 'pr:approved', color: '0e8a16', description: 'PR approved, ready for merge' },
751
+ { name: 'architecture-violation', color: 'd93f0b', description: 'Invariant violation detected by CI' },
752
+ { name: 'qa:approved', color: '0e8a16', description: 'QA Agent approved the implementation' },
753
+ { name: 'qa:needs-work', color: 'd93f0b', description: 'QA Agent found issues' },
754
+ { name: 'infra:qa-broken', color: 'F97316', description: 'QA Agent failed to run — manual review required' },
755
+ { name: 'jules', color: '5319e7', description: 'Jules AI Agent' }
756
+ ];
757
+
758
+ console.log(`\n${cyan}${i18n.creating_labels}${reset}`);
759
+ for (const label of labels) {
760
+ try {
761
+ execFileSync('gh', ['label', 'create', label.name, '--color', label.color, '--description', label.description, '--repo', repo, '--force'], { stdio: 'ignore' });
762
+ console.log(` ${i18n.label_ok}${label.name}`);
763
+ } catch (err) {
764
+ console.log(` ${i18n.label_warn}${label.name}`);
765
+ }
766
+ }
767
+
768
+ // Board
769
+ console.log(`\n${cyan}${i18n.creating_project}${reset}`);
770
+ let ownerId, projectId, projectNumber;
771
+ try {
772
+ if (isOrg) {
773
+ ownerId = execFileSync('gh', ['api', 'graphql', '-f', 'query=query($login: String!) { organization(login: $login) { id } }', '-f', `login=${repoOwner}`, '--jq', '.data.organization.id'],
774
+ { stdio: ['ignore', 'pipe', 'pipe'] }).toString().trim();
775
+ } else {
776
+ ownerId = execFileSync('gh', ['api', 'graphql', '-f', 'query={ viewer { id } }', '--jq', '.data.viewer.id'],
777
+ { stdio: ['ignore', 'pipe', 'pipe'] }).toString().trim();
778
+ }
779
+
780
+ const raw = execFileSync('gh', ['api', 'graphql', '-f',
781
+ 'query=mutation($owner: ID!, $title: String!) { createProjectV2(input: {ownerId: $owner, title: $title}) { projectV2 { id number } } }',
782
+ '-f', `owner=${ownerId}`, '-f', `title=${boardName}`],
783
+ { stdio: ['ignore', 'pipe', 'pipe'] }).toString().trim();
784
+ const resp = raw ? JSON.parse(raw) : null;
785
+ if (resp?.errors) throw new Error(resp.errors.map(e => e.message).join('; '));
786
+ const pData = resp?.data?.createProjectV2?.projectV2;
787
+ projectId = pData?.id;
788
+ projectNumber = pData?.number;
789
+ console.log(` ${i18n.project_ok}${projectId})`);
790
+
791
+ try {
792
+ const repoNodeId = execFileSync('gh', ['api', `repos/${repo}`, '--jq', '.node_id'],
793
+ { stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim();
794
+ execFileSync('gh', ['api', 'graphql', '-f',
795
+ 'query=mutation($projectId: ID!, $repositoryId: ID!) { linkProjectV2ToRepository(input: {projectId: $projectId, repositoryId: $repositoryId}) { repository { name } } }',
796
+ '-f', `projectId=${projectId}`, '-f', `repositoryId=${repoNodeId}`],
797
+ { stdio: 'ignore' });
798
+ console.log(` ${i18n.link_project_ok}`);
799
+ } catch (_) {
800
+ console.log(` ${i18n.link_project_warn}`);
801
+ }
802
+ } catch (err) {
803
+ console.log(` ${i18n.project_err}${err.message}`);
804
+ }
805
+
806
+ let statusFieldId;
807
+ let optionMap = {};
808
+
809
+ if (projectId) {
810
+ console.log(` ${cyan}${i18n.config_columns}${reset}`);
811
+ try {
812
+ statusFieldId = execFileSync('gh', ['api', 'graphql', '-f',
813
+ 'query=query($projectId: ID!) { node(id: $projectId) { ... on ProjectV2 { fields(first: 20) { nodes { ... on ProjectV2SingleSelectField { id name } } } } } }',
814
+ '-f', `projectId=${projectId}`, '--jq', '.data.node.fields.nodes[] | select(.name == "Status") | .id'
815
+ ]).toString().trim();
816
+
817
+ if (statusFieldId) {
818
+ const columns = [
819
+ { name: '💡 Idea - No move to Exploration directly', description: 'Just tell your agent to work on issue #XX', color: 'GRAY' },
820
+ { name: '🔍 Exploration', description: 'AI is analyzing code and context', color: 'PURPLE' },
821
+ { name: '🧠 Brainstorming', description: 'AI proposed approaches and trade-offs', color: 'PINK' },
822
+ { name: '📐 Detail Solution', description: 'AI is writing the technical spec', color: 'BLUE' },
823
+ { name: '✅ Approval', description: 'Spec ready, awaiting `spec:approved` label', color: 'GREEN' },
824
+ { name: '⚙️ Development', description: 'AI implementing the spec', color: 'ORANGE' },
825
+ { name: '🧪 Testing', description: 'QA testing and CI pipeline checks', color: 'RED' },
826
+ { name: '👁 Code Review / PR',description: 'PR opened, awaiting your review', color: 'YELLOW' },
827
+ { name: '🚀 Ready for Production', description: 'Merged and ready for production', color: 'GREEN' }
828
+ ];
829
+
830
+ const queryPayload = JSON.stringify({
831
+ query: `mutation($fieldId: ID!, $options: [ProjectV2SingleSelectFieldOptionInput!]) {
832
+ updateProjectV2Field(input: { fieldId: $fieldId, singleSelectOptions: $options }) {
833
+ projectV2Field { ... on ProjectV2SingleSelectField { options { id name } } }
834
+ }
835
+ }`,
836
+ variables: { fieldId: statusFieldId, options: columns }
837
+ });
838
+
839
+ const updateOutput = execFileSync('gh', ['api', 'graphql', '--input', '-'],
840
+ { input: queryPayload }).toString().trim();
841
+ const jsonResponse = updateOutput ? JSON.parse(updateOutput) : null;
842
+ const returnedOptions = jsonResponse?.data?.updateProjectV2Field?.projectV2Field?.options || [];
843
+ for (const opt of returnedOptions) optionMap[opt.name] = opt.id;
844
+ console.log(` ${i18n.columns_ok}`);
845
+ }
846
+ } catch (_) {
847
+ console.log(` ${i18n.columns_warn}`);
848
+ }
849
+ }
850
+
851
+ // Auto-provision PROJECT_PAT for personal repos
852
+ let patAutoSet = false;
853
+ if (projectId && !isOrg) {
854
+ try {
855
+ const tokenOut = execFileSync('gh', ['auth', 'token'],
856
+ { stdio: ['ignore', 'pipe', 'pipe'], encoding: 'utf8' }).trim();
857
+ if (tokenOut) {
858
+ execFileSync('gh', ['secret', 'set', 'PROJECT_PAT', '--body', tokenOut, '--repo', repo],
859
+ { stdio: ['ignore', 'pipe', 'pipe'] });
860
+ patAutoSet = true;
861
+ console.log(`\n${green}✅ PROJECT_PAT secret set automatically (uses your gh OAuth token).${reset}`);
862
+ }
863
+ } catch (_) {
864
+ console.log(`\n${yellow}⚠️ Could not auto-set PROJECT_PAT. Agent will guide manual setup.${reset}`);
865
+ }
866
+ }
867
+
868
+ console.log(`\n${yellow}${i18n.scaffolding}${reset}`);
869
+ scaffoldFullTemplates(sourceDir, targetDir, projectId, statusFieldId, optionMap, repoOwner, repoName);
870
+
871
+ await setBranchProtection(repo, ['PDLC Stage Gate', 'QA Gate']);
872
+
873
+ const boardUrl = projectNumber ? buildBoardUrl(repoOwner, projectNumber, isOrg) : null;
874
+ writeCliContext(targetDir, 'full', {
875
+ projectName,
876
+ repoOwner,
877
+ repoName,
878
+ projectNumber,
879
+ isOrg,
880
+ boardUrl,
881
+ patAutoSet
882
+ });
883
+
884
+ const line1 = t('🎉 Upgrade complete! Board:', '🎉 Upgrade concluído! Board:', '🎉 ¡Actualización completada! Board:');
885
+ console.log(`\n${green}${line1} ${boardUrl || '(board creation failed)'}${reset}\n`);
886
+
887
+ rl.close();
888
+ }
889
+ ```
890
+
891
+ - [ ] **Step 2: Syntax check**
892
+
893
+ ```bash
894
+ node --check bin/cli.js
895
+ ```
896
+
897
+ Expected: no output.
898
+
899
+ - [ ] **Step 3: Commit**
900
+
901
+ ```bash
902
+ git add bin/cli.js
903
+ git commit -m "feat(cli): implement runUpgradeToAgentic — upgrade lite to full"
904
+ ```
905
+
906
+ ---
907
+
908
+ ## Task 7: Backwards compat for `runUpdate()` with missing `profile` field
909
+
910
+ **Files:**
911
+ - Modify: `bin/cli.js` — `runUpdate()` function
912
+
913
+ `runUpdate()` reads `cli-context.json`. Existing installs lack the `profile` field. The function must treat missing `profile` as `'full'` to avoid breakage.
914
+
915
+ - [ ] **Step 1: Locate the context read in `runUpdate()`**
916
+
917
+ Find (approximately line 615 of original):
918
+ ```js
919
+ async function runUpdate() {
920
+ const contextPath = path.join(targetDir, '.agentic-pdlc', 'cli-context.json');
921
+ if (!fs.existsSync(contextPath)) {
922
+ console.error(`\n${red}${i18n.update_no_context}${reset}\n`);
923
+ rl.close();
924
+ process.exit(1);
925
+ }
926
+
927
+ const state = detectAgentState(targetDir);
928
+ ```
929
+
930
+ - [ ] **Step 2: Add backwards-compat profile check**
931
+
932
+ Insert after the `fs.existsSync` guard and before `const state = detectAgentState(targetDir);`:
933
+
934
+ ```js
935
+ const ctx = JSON.parse(fs.readFileSync(contextPath, 'utf8'));
936
+ if ((ctx.profile || 'full') === 'lite') {
937
+ console.log(`\n${yellow}⚠️ Lite install detected. Run --upgrade-to-agentic to add the full board machine first.${reset}\n`);
938
+ rl.close();
939
+ return;
940
+ }
941
+ ```
942
+
943
+ This guards against accidentally running `--update` (which configures Jules/QA/Sentinel) on a lite install that has no board.
944
+
945
+ - [ ] **Step 3: Syntax check**
946
+
947
+ ```bash
948
+ node --check bin/cli.js
949
+ ```
950
+
951
+ Expected: no output.
952
+
953
+ - [ ] **Step 4: Commit**
954
+
955
+ ```bash
956
+ git add bin/cli.js
957
+ git commit -m "fix(cli): guard runUpdate against lite installs, treat missing profile as full"
958
+ ```
959
+
960
+ ---
961
+
962
+ ## Task 8: Run all tests + final integration check
963
+
964
+ - [ ] **Step 1: Run unit tests**
965
+
966
+ ```bash
967
+ npm test
968
+ ```
969
+
970
+ Expected: all `resolveMode` and `buildFullClaudeContent` tests pass.
971
+
972
+ - [ ] **Step 2: Dry-run syntax check on final cli.js**
973
+
974
+ ```bash
975
+ node --check bin/cli.js
976
+ ```
977
+
978
+ Expected: no output.
979
+
980
+ - [ ] **Step 3: Smoke-test lite mode (no real repo needed)**
981
+
982
+ The `--help` flag doesn't exist, but you can verify the routing by reading the entry point:
983
+
984
+ ```bash
985
+ node -e "
986
+ const { execSync } = require('child_process');
987
+ // Patch: override process.argv to simulate no-flag invocation
988
+ // Then verify resolveMode is in scope and returns 'lite'
989
+ const src = require('fs').readFileSync('bin/cli.js', 'utf8');
990
+ const match = src.match(/function resolveMode\(args\)/);
991
+ console.log(match ? '✅ resolveMode found' : '❌ resolveMode missing');
992
+ const entryLite = src.includes(\"mode === 'lite'\") || src.includes('runLiteSetup');
993
+ const entryFull = src.includes(\"mode === 'full'\") || src.includes('runFullSetup');
994
+ const entryUpgrade = src.includes('runUpgradeToAgentic');
995
+ console.log(entryLite ? '✅ runLiteSetup wired' : '❌ runLiteSetup missing');
996
+ console.log(entryFull ? '✅ runFullSetup wired' : '❌ runFullSetup missing');
997
+ console.log(entryUpgrade ? '✅ runUpgradeToAgentic wired' : '❌ runUpgradeToAgentic missing');
998
+ "
999
+ ```
1000
+
1001
+ Expected:
1002
+ ```
1003
+ ✅ resolveMode found
1004
+ ✅ runLiteSetup wired
1005
+ ✅ runFullSetup wired
1006
+ ✅ runUpgradeToAgentic wired
1007
+ ```
1008
+
1009
+ - [ ] **Step 4: Verify template paths exist**
1010
+
1011
+ ```bash
1012
+ node -e "
1013
+ const fs = require('fs');
1014
+ const checks = [
1015
+ 'templates/lite/CLAUDE.md',
1016
+ 'templates/lite/AGENTS.md',
1017
+ 'templates/full/CLAUDE.md',
1018
+ 'templates/full/AGENTS.md',
1019
+ 'templates/full/docs/pdlc.md',
1020
+ 'templates/.github/ISSUE_TEMPLATE',
1021
+ 'templates/.github/workflows/project-automation.yml',
1022
+ 'adapters/hooks/pdlc-stage-gate.sh',
1023
+ ];
1024
+ for (const p of checks) {
1025
+ console.log(fs.existsSync(p) ? '✅ ' + p : '❌ MISSING: ' + p);
1026
+ }
1027
+ "
1028
+ ```
1029
+
1030
+ Expected: all `✅`.
1031
+
1032
+ - [ ] **Step 5: Final commit**
1033
+
1034
+ ```bash
1035
+ git add bin/cli.js tests/cli.test.js package.json
1036
+ git commit -m "test: final integration checks pass"
1037
+ ```
1038
+
1039
+ ---
1040
+
1041
+ ## Self-Review
1042
+
1043
+ ### Spec coverage
1044
+
1045
+ | AC | Task |
1046
+ |---|---|
1047
+ | `npx create-agentic-pdlc` installs only lite artifacts | Task 4 — `runLiteSetup()` |
1048
+ | `--agentic` installs lite + board workflows | Task 5 — `runFullSetup()` |
1049
+ | `--update` on lite preserves board IDs | Task 7 — guard + no-op on lite |
1050
+ | `--upgrade-to-agentic` adds board without touching lite config | Task 6 — `runUpgradeToAgentic()` |
1051
+ | Legacy install (no `profile` field) treated as `full` | Task 7 — `(ctx.profile \|\| 'full')` |
1052
+
1053
+ ### Known gaps
1054
+
1055
+ - `scaffoldLiteTemplates` does not copy a `pdlc-stage-gate.yml` CI workflow to the target `.github/workflows/` — lite uses only branch protection, no CI workflow. This is intentional per the spec ("No workflows beyond branch protection and CI"). If a minimal `pdlc-stage-gate.yml` is needed for the required status check to appear in GitHub, add a step in Task 4 to also copy `templates/.github/workflows/pdlc-stage-gate.yml` to the lite scaffolding.
1056
+ - `runUpgradeToAgentic()` duplicates the board creation logic from `runFullSetup()`. This is acceptable at this scope (Option B). If duplication becomes a burden, extract a `createBoard(repo, boardName, isOrg)` helper — that's a future cleanup, not in scope here.