create-agentic-pdlc 2.4.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.
Files changed (33) hide show
  1. package/.agentic-pdlc/hooks/pdlc-stage-gate.sh +37 -10
  2. package/.claude/settings.json +18 -0
  3. package/.coderabbit.yaml +41 -0
  4. package/.github/workflows/add-to-board.yml +55 -7
  5. package/.github/workflows/agent-trigger.yml +57 -25
  6. package/.github/workflows/board-reconciliation.yml +176 -0
  7. package/.github/workflows/pdlc-health-check.yml +81 -81
  8. package/.github/workflows/project-automation.yml +252 -259
  9. package/CLAUDE.md +1 -1
  10. package/README.md +33 -32
  11. package/adapters/claude-code/skill.md +12 -8
  12. package/bin/cli.js +607 -213
  13. package/docs/superpowers/plans/2026-06-04-spec-format-issue-template.md +160 -0
  14. package/docs/superpowers/plans/2026-06-04-two-tier-installer.md +1056 -0
  15. package/docs/superpowers/plans/2026-06-05-archive-card-on-issue-close.md +105 -0
  16. package/docs/superpowers/plans/2026-06-05-project-id-actions-variable.md +336 -0
  17. package/docs/superpowers/specs/2026-06-04-spec-format-issue-template-design.md +46 -0
  18. package/docs/superpowers/specs/2026-06-05-project-id-actions-variable-design.md +114 -0
  19. package/package.json +2 -2
  20. package/scripts/derive-column.js +20 -0
  21. package/templates/.github/workflows/add-to-board.yml +2 -2
  22. package/templates/.github/workflows/agent-trigger.yml +2 -2
  23. package/templates/.github/workflows/pdlc-health-check.yml +2 -2
  24. package/templates/.github/workflows/project-automation.yml +47 -8
  25. package/templates/full/CLAUDE.md +30 -0
  26. package/templates/lite/AGENTS.md +121 -0
  27. package/templates/lite/CLAUDE.md +44 -0
  28. package/tests/cli.test.js +118 -0
  29. package/.github/workflows/agentic-metrics.yml +0 -545
  30. package/.github/workflows/qa-agent.yml +0 -139
  31. package/.github/workflows/qa-gate.yml +0 -51
  32. /package/templates/{AGENTS.md → full/AGENTS.md} +0 -0
  33. /package/templates/{docs → full/docs}/pdlc.md +0 -0
package/bin/cli.js CHANGED
@@ -10,12 +10,12 @@ const targetDir = process.cwd();
10
10
  // The directory where this script sits (globally/locally in node_modules)
11
11
  const sourceDir = path.join(__dirname, '..');
12
12
 
13
- const rl = readline.createInterface({
14
- input: process.stdin,
15
- output: process.stdout
16
- });
13
+ const rl = require.main === module
14
+ ? readline.createInterface({ input: process.stdin, output: process.stdout })
15
+ : null;
17
16
 
18
17
  function askQuestion(query) {
18
+ if (!rl) throw new Error('askQuestion called in non-interactive context');
19
19
  return new Promise(resolve => rl.question(query, resolve));
20
20
  }
21
21
 
@@ -60,6 +60,7 @@ const i18n = {
60
60
  cursor_rules_written: t('✅ Default cursor rules written to .cursorrules', '✅ Regras padrão do cursor salvas em .cursorrules', '✅ Reglas por defecto de cursor guardadas en .cursorrules'),
61
61
  setup_done: t('🎉 All set! Continue the setup with your agent:', '🎉 Aqui tá pronto! Continue o setup com o seu agente:', '🎉 ¡Listo! Continúa el setup con tu agente:'),
62
62
  setup_done_hint: t('>>> Tell it to read and execute the .agentic-setup.md file!', '>>> Diga a ele para ler e executar o arquivo .agentic-setup.md!', '>>> Dile que lea y ejecute el archivo .agentic-setup.md!'),
63
+ upgrade_hint: t('💡 To add the full board + multi-agent automation later: npx create-agentic-pdlc --upgrade-to-agentic', '💡 Para adicionar o board completo + automação multi-agente mais tarde: npx create-agentic-pdlc --upgrade-to-agentic', '💡 Para agregar el tablero completo + automatización multi-agente más tarde: npx create-agentic-pdlc --upgrade-to-agentic'),
63
64
  update_title: t('agentic-pdlc — Agent Configuration Status', 'agentic-pdlc — Status de Configuração dos Agentes', 'agentic-pdlc — Estado de Configuración de Agentes'),
64
65
  update_no_context: t('❌ No .agentic-pdlc/cli-context.json found. Run npx create-agentic-pdlc first.', '❌ Arquivo .agentic-pdlc/cli-context.json não encontrado. Rode npx create-agentic-pdlc primeiro.', '❌ Archivo .agentic-pdlc/cli-context.json no encontrado. Ejecuta npx create-agentic-pdlc primero.'),
65
66
  update_all_ok: t('All agents configured!', 'Todos os agentes configurados!', '¡Todos los agentes configurados!'),
@@ -70,11 +71,26 @@ const i18n = {
70
71
  update_jules_ask_handle: t(' Agent handle (e.g. @my-agent): ', ' Handle do agente (ex: @meu-agente): ', ' Handle del agente (ej: @mi-agente): '),
71
72
  update_qa_header: t('— QA Agent (AC verification via GitHub Models — zero secrets) —', '— QA Agent (verificação de ACs via GitHub Models — zero secrets) —', '— QA Agent (verificación de ACs via GitHub Models — zero secrets) —'),
72
73
  update_qa_ask: t(' Activate? Uses GITHUB_TOKEN — no extra secrets needed. (Y/n): ', ' Ativar? Usa GITHUB_TOKEN — nenhum secret extra necessário. (S/n): ', ' ¿Activar? Usa GITHUB_TOKEN — sin secrets adicionales. (S/n): '),
73
- update_sentinel_header: t('— Sentinel (architecture audit via Gemini Code Assist) —', '— Sentinel (auditoria de arquitetura via Gemini Code Assist) —', '— Sentinel (auditoría de arquitectura via Gemini Code Assist) —'),
74
- update_sentinel_ask: t(' Activate? Requires Gemini Code Assist CI job. (Y/n): ', ' Ativar? Requer CI job do Gemini Code Assist. (S/n): ', ' ¿Activar? Requiere CI job de Gemini Code Assist. (S/n): '),
74
+ update_sentinel_header: t('— Sentinel (architecture-violation label board automation) —', '— Sentinel (label architecture-violation automação de board) —', '— Sentinel (label architecture-violation automatización de board) —'),
75
+ update_sentinel_ask: t(" Activate? CodeRabbit applies 'architecture-violation' label when violations are detected. (Y/n): ", " Ativar? CodeRabbit aplica a label 'architecture-violation' quando detecta violações. (S/n): ", " ¿Activar? CodeRabbit aplica la etiqueta 'architecture-violation' cuando detecta violaciones. (S/n): "),
75
76
  configuring_protection: t('[3/3] Configuring branch protection...', '[3/3] Configurando proteção de branch...', '[3/3] Configurando protección de rama...'),
76
77
  protection_ok: t('✅ Branch protection set — required checks: PDLC Stage Gate, QA Gate.', '✅ Proteção de branch configurada — checks obrigatórios: PDLC Stage Gate, QA Gate.', '✅ Protección de rama configurada — checks requeridos: PDLC Stage Gate, QA Gate.'),
77
78
  protection_warn: t('⚠️ Branch protection could not be set automatically.\n Set required checks manually: Settings → Branches → main → Required status checks.\n Required: "PDLC Stage Gate" and "QA Gate"', '⚠️ Proteção de branch não pôde ser configurada automaticamente.\n Configure manualmente: Settings → Branches → main → Required status checks.\n Obrigatórios: "PDLC Stage Gate" e "QA Gate"', '⚠️ No se pudo configurar la protección de rama automáticamente.\n Configúralo en: Settings → Branches → main → Required status checks.\n Requeridos: "PDLC Stage Gate" y "QA Gate"'),
79
+ issue_templates_copied: t(
80
+ '✅ Issue templates copied to .github/ISSUE_TEMPLATE/',
81
+ '✅ Issue templates copiados para .github/ISSUE_TEMPLATE/',
82
+ '✅ Issue templates copiados a .github/ISSUE_TEMPLATE/'
83
+ ),
84
+ vars_project_id_ok: t(
85
+ '✅ vars.PROJECT_ID set as Actions Variable.',
86
+ '✅ vars.PROJECT_ID configurado como Variável do Actions.',
87
+ '✅ vars.PROJECT_ID configurado como Variable de Actions.'
88
+ ),
89
+ vars_project_id_warn: t(
90
+ '⚠️ Could not set vars.PROJECT_ID — token may lack variables:write scope.\n Set manually: repo Settings → Secrets and variables → Variables → PROJECT_ID = ',
91
+ '⚠️ Não foi possível configurar vars.PROJECT_ID — o token pode não ter permissão variables:write.\n Configure manualmente: repo Settings → Secrets and variables → Variables → PROJECT_ID = ',
92
+ '⚠️ No se pudo configurar vars.PROJECT_ID — el token puede no tener permiso variables:write.\n Configura manualmente: repo Settings → Secrets and variables → Variables → PROJECT_ID = '
93
+ ),
78
94
  };
79
95
 
80
96
  const cyan = '\x1b[36m';
@@ -83,9 +99,21 @@ const green = '\x1b[32m';
83
99
  const yellow = '\x1b[33m';
84
100
  const red = '\x1b[31m';
85
101
 
86
- console.log(`${cyan}================================================================${reset}`);
87
- console.log(`${cyan}${i18n.welcome}${reset}`);
88
- console.log(`${cyan}================================================================${reset}\n`);
102
+ const PDLC_LABELS = [
103
+ { name: 'stage:exploration', color: '9b59b6', description: 'Issue is being evaluated' },
104
+ { name: 'stage:brainstorming', color: 'e84393', description: 'Proposed approaches awaiting PM gate' },
105
+ { name: 'stage:detailing', color: '3498db', description: 'Technical spec is being written' },
106
+ { name: 'stage:development', color: 'e67e22', description: 'Agent is implementing the spec' },
107
+ { name: 'stage:testing', color: '8e44ad', description: 'Agent is testing the implementation' },
108
+ { name: 'spec:approved', color: '0e8a16', description: 'Spec approved — agent can implement' },
109
+ { name: 'pr:in-review', color: 'e4e669', description: 'PR awaiting code review' },
110
+ { name: 'pr:approved', color: '0e8a16', description: 'PR approved, ready for merge' },
111
+ { name: 'architecture-violation', color: 'd93f0b', description: 'Invariant violation detected by CI' },
112
+ { name: 'qa:approved', color: '0e8a16', description: 'QA Agent approved the implementation' },
113
+ { name: 'qa:needs-work', color: 'd93f0b', description: 'QA Agent found issues' },
114
+ { name: 'infra:qa-broken', color: 'F97316', description: 'QA Agent failed to run — manual review required' },
115
+ { name: 'jules', color: '5319e7', description: 'Jules AI Agent' }
116
+ ];
89
117
 
90
118
  function buildBoardUrl(repoOwner, projectNumber, isOrg) {
91
119
  const segment = isOrg ? 'orgs' : 'users';
@@ -118,7 +146,9 @@ function printSetupDone() {
118
146
  console.log(`${green}${sep}${reset}\n`);
119
147
  }
120
148
 
121
- async function runSetup() {
149
+ // ─── Shared helper functions ──────────────────────────────────────────────────
150
+
151
+ async function checkGhAuth() {
122
152
  console.log(`${yellow}${i18n.checking_gh}${reset}`);
123
153
  try {
124
154
  execSync('gh auth status', { stdio: 'ignore' });
@@ -126,46 +156,258 @@ async function runSetup() {
126
156
  } catch (error) {
127
157
  console.error(`${red}${i18n.gh_error}${reset}`);
128
158
  console.error(`${i18n.gh_install}`);
159
+ rl.close();
129
160
  process.exit(1);
130
161
  }
162
+ }
131
163
 
132
- function getScopes() {
133
- try {
134
- const out = execFileSync('gh', ['api', 'user', '-i'], { stdio: ['ignore', 'pipe', 'pipe'], encoding: 'utf8' });
135
- const line = out.split('\n').find(l => l.toLowerCase().startsWith('x-oauth-scopes:'));
136
- return line ? line.split(':').slice(1).join(':').split(',').map(s => s.trim()) : [];
137
- } catch (e) {
138
- return [];
139
- }
164
+ function getScopes() {
165
+ try {
166
+ const out = execFileSync('gh', ['api', 'user', '-i'], { stdio: ['ignore', 'pipe', 'pipe'], encoding: 'utf8' });
167
+ const line = out.split('\n').find(l => l.toLowerCase().startsWith('x-oauth-scopes:'));
168
+ return line ? line.split(':').slice(1).join(':').split(',').map(s => s.trim()) : [];
169
+ } catch (e) {
170
+ return [];
140
171
  }
172
+ }
141
173
 
174
+ async function checkAndRefreshProjectScope() {
142
175
  const scopesBefore = getScopes();
143
- if (scopesBefore.length > 0 && !scopesBefore.includes('project')) {
144
- console.log(`${yellow}⚠️ Token missing 'project' scope — required for GitHub Projects board.${reset}`);
145
- console.log(`${yellow} Refreshing token now (browser may open)...${reset}\n`);
146
- try {
147
- execSync('gh auth refresh -h github.com -s project', { stdio: 'inherit' });
148
- } catch (e) {
149
- console.log(`${red} Token refresh failed. Run manually: gh auth refresh -h github.com -s project${reset}`);
150
- rl.close();
151
- process.exit(1);
176
+ if (scopesBefore.length === 0 || scopesBefore.includes('project')) return;
177
+
178
+ console.log(`${yellow}⚠️ Token missing 'project' scope required for GitHub Projects board.${reset}`);
179
+ console.log(`${yellow} Refreshing token now (browser may open)...${reset}\n`);
180
+ try {
181
+ execSync('gh auth refresh -h github.com -s project', { stdio: 'inherit' });
182
+ } catch (e) {
183
+ console.log(`${red}❌ Token refresh failed. Run manually: gh auth refresh -h github.com -s project${reset}`);
184
+ rl.close();
185
+ process.exit(1);
186
+ }
187
+ const scopesAfter = getScopes();
188
+ if (scopesAfter.length > 0 && !scopesAfter.includes('project')) {
189
+ console.log(`\n${red}❌ 'project' scope still missing after refresh.${reset}`);
190
+ console.log(`${yellow} Active scopes: ${scopesAfter.join(', ')}${reset}`);
191
+ console.log(`${yellow} Try manually: gh auth refresh -h github.com -s project${reset}`);
192
+ rl.close();
193
+ process.exit(1);
194
+ }
195
+ if (scopesAfter.length > 0) {
196
+ console.log(`\n${green}✅ Token refreshed. Active scopes: ${scopesAfter.join(', ')}${reset}\n`);
197
+ } else {
198
+ console.log(`\n${green}✅ Token refreshed with 'project' scope.${reset}\n`);
199
+ }
200
+ }
201
+
202
+ function installHook(sourceDir, targetDir) {
203
+ const hookSrc = path.join(sourceDir, 'adapters', 'hooks', 'pdlc-stage-gate.sh');
204
+ const hookDir = path.join(targetDir, '.agentic-pdlc', 'hooks');
205
+ const hookDest = path.join(hookDir, 'pdlc-stage-gate.sh');
206
+ if (!fs.existsSync(hookSrc)) return;
207
+
208
+ fs.mkdirSync(hookDir, { recursive: true });
209
+ fs.copyFileSync(hookSrc, hookDest);
210
+ fs.chmodSync(hookDest, '755');
211
+
212
+ const settingsDir = path.join(targetDir, '.claude');
213
+ const settingsPath = path.join(settingsDir, 'settings.json');
214
+ if (!fs.existsSync(settingsPath)) {
215
+ fs.mkdirSync(settingsDir, { recursive: true });
216
+ fs.writeFileSync(settingsPath, JSON.stringify({
217
+ hooks: {
218
+ PreToolUse: [{
219
+ matcher: 'Bash',
220
+ hooks: [{ type: 'command', command: 'bash .agentic-pdlc/hooks/pdlc-stage-gate.sh' }]
221
+ }]
222
+ }
223
+ }, null, 2) + '\n');
224
+ }
225
+ }
226
+
227
+ async function setBranchProtection(repo, requiredChecks) {
228
+ console.log(`\n${cyan}${i18n.configuring_protection}${reset}`);
229
+ try {
230
+ const defaultBranch = execFileSync(
231
+ 'gh', ['api', `repos/${repo}`, '--jq', '.default_branch'],
232
+ { stdio: ['ignore', 'pipe', 'ignore'], encoding: 'utf8' }
233
+ ).trim() || 'main';
234
+
235
+ const protectionPayload = JSON.stringify({
236
+ required_status_checks: { strict: false, contexts: requiredChecks },
237
+ enforce_admins: false,
238
+ required_pull_request_reviews: null,
239
+ restrictions: null
240
+ });
241
+
242
+ execFileSync(
243
+ 'gh',
244
+ ['api', `repos/${repo}/branches/${defaultBranch}/protection`, '--method', 'PUT', '--input', '-'],
245
+ { input: protectionPayload, stdio: ['pipe', 'ignore', 'pipe'] }
246
+ );
247
+ console.log(` ${green}${i18n.protection_ok}${reset}`);
248
+ } catch (_) {
249
+ console.log(` ${yellow}${i18n.protection_warn}${reset}`);
250
+ }
251
+ }
252
+
253
+ function copyAdapterFiles(agent, sourceDir, targetDir) {
254
+ const claudeSetupSrc = path.join(sourceDir, 'adapters', 'claude-code', 'skill.md');
255
+ const cursorSetupSrc = path.join(sourceDir, 'adapters', 'cursor', 'rules.md');
256
+
257
+ if (agent === 'cursor') {
258
+ if (fs.existsSync(cursorSetupSrc)) {
259
+ fs.copyFileSync(cursorSetupSrc, path.join(targetDir, '.cursorrules'));
260
+ console.log(`${i18n.cursor_rules_written}`);
152
261
  }
153
- const scopesAfter = getScopes();
154
- if (scopesAfter.length > 0 && !scopesAfter.includes('project')) {
155
- console.log(`\n${red}❌ 'project' scope still missing after refresh.${reset}`);
156
- console.log(`${yellow} Active scopes: ${scopesAfter.join(', ')}${reset}`);
157
- console.log(`${yellow} Note: gh uses OAuth tokens — visible at github.com/settings/applications, not /settings/tokens${reset}`);
158
- console.log(`${yellow} Try manually: gh auth refresh -h github.com -s project${reset}`);
159
- rl.close();
160
- process.exit(1);
262
+ }
263
+ if (fs.existsSync(claudeSetupSrc)) {
264
+ fs.copyFileSync(claudeSetupSrc, path.join(targetDir, '.agentic-setup.md'));
265
+ console.log(`${i18n.setup_written}`);
266
+ printSetupDone();
267
+ } else {
268
+ console.error(`${i18n.missing_claude}${claudeSetupSrc}`);
269
+ }
270
+ }
271
+
272
+ function scaffoldLiteTemplates(sourceDir, targetDir) {
273
+ const destTemplates = path.join(targetDir, '.agentic-pdlc', 'templates');
274
+ fs.mkdirSync(destTemplates, { recursive: true });
275
+
276
+ // CLAUDE.md — lite version
277
+ const liteClaudeSrc = path.join(sourceDir, 'templates', 'lite', 'CLAUDE.md');
278
+ if (fs.existsSync(liteClaudeSrc)) {
279
+ fs.copyFileSync(liteClaudeSrc, path.join(destTemplates, 'CLAUDE.md'));
280
+ }
281
+
282
+ // AGENTS.md — lite version
283
+ const liteAgentsSrc = path.join(sourceDir, 'templates', 'lite', 'AGENTS.md');
284
+ if (fs.existsSync(liteAgentsSrc)) {
285
+ fs.copyFileSync(liteAgentsSrc, path.join(destTemplates, 'AGENTS.md'));
286
+ }
287
+
288
+ // Issue templates — shared between lite and full
289
+ const issueTemplateSrc = path.join(sourceDir, 'templates', '.github', 'ISSUE_TEMPLATE');
290
+ const issueTemplateDest = path.join(destTemplates, '.github', 'ISSUE_TEMPLATE');
291
+ if (fs.existsSync(issueTemplateSrc)) {
292
+ copyDirSync(issueTemplateSrc, issueTemplateDest);
293
+ }
294
+ }
295
+
296
+ function setActionsVariable(repo, name, value, execFn = execFileSync) {
297
+ try {
298
+ execFn('gh', [
299
+ 'api', `repos/${repo}/actions/variables/${name}`,
300
+ '--method', 'PATCH',
301
+ '-f', `name=${name}`,
302
+ '-f', `value=${value}`
303
+ ], { stdio: ['ignore', 'pipe', 'pipe'] });
304
+ } catch (err) {
305
+ const msg = (err.stderr?.toString() || '') + (err.message || '');
306
+ if (msg.includes('404') || msg.includes('Not Found')) {
307
+ execFn('gh', [
308
+ 'api', `repos/${repo}/actions/variables`,
309
+ '--method', 'POST',
310
+ '-f', `name=${name}`,
311
+ '-f', `value=${value}`
312
+ ], { stdio: ['ignore', 'pipe', 'pipe'] });
313
+ } else {
314
+ throw err;
315
+ }
316
+ }
317
+ }
318
+
319
+ function scaffoldFullTemplates(sourceDir, targetDir, projectId, statusFieldId, optionMap, repoOwner, repoName) {
320
+ const destTemplates = path.join(targetDir, '.agentic-pdlc', 'templates');
321
+ fs.mkdirSync(destTemplates, { recursive: true });
322
+
323
+ // CLAUDE.md — concatenate lite + full addon
324
+ const liteClaudeSrc = path.join(sourceDir, 'templates', 'lite', 'CLAUDE.md');
325
+ const fullClaudeSrc = path.join(sourceDir, 'templates', 'full', 'CLAUDE.md');
326
+ if (fs.existsSync(liteClaudeSrc) && fs.existsSync(fullClaudeSrc)) {
327
+ const combined = fs.readFileSync(liteClaudeSrc, 'utf8') + '\n' + fs.readFileSync(fullClaudeSrc, 'utf8');
328
+ fs.writeFileSync(path.join(destTemplates, 'CLAUDE.md'), combined);
329
+ } else if (fs.existsSync(liteClaudeSrc)) {
330
+ fs.copyFileSync(liteClaudeSrc, path.join(destTemplates, 'CLAUDE.md'));
331
+ }
332
+
333
+ // AGENTS.md — full version
334
+ const fullAgentsSrc = path.join(sourceDir, 'templates', 'full', 'AGENTS.md');
335
+ if (fs.existsSync(fullAgentsSrc)) {
336
+ fs.copyFileSync(fullAgentsSrc, path.join(destTemplates, 'AGENTS.md'));
337
+ }
338
+
339
+ // All of templates/.github/ (issue templates + workflows)
340
+ const githubSrc = path.join(sourceDir, 'templates', '.github');
341
+ const githubDest = path.join(destTemplates, '.github');
342
+ if (fs.existsSync(githubSrc)) {
343
+ copyDirSync(githubSrc, githubDest);
344
+ }
345
+
346
+ // docs/pdlc.md — substitute board IDs
347
+ const pdlcSrc = path.join(sourceDir, 'templates', 'full', 'docs', 'pdlc.md');
348
+ const pdlcDest = path.join(destTemplates, 'docs', 'pdlc.md');
349
+ if (fs.existsSync(pdlcSrc)) {
350
+ fs.mkdirSync(path.join(destTemplates, 'docs'), { recursive: true });
351
+ let pdlcContent = fs.readFileSync(pdlcSrc, 'utf8');
352
+ if (projectId) pdlcContent = pdlcContent.replace(/\{\{PROJECT_ID\}\}/g, () => projectId);
353
+ if (statusFieldId) pdlcContent = pdlcContent.replace(/\{\{STATUS_FIELD_ID\}\}/g, () => statusFieldId);
354
+ pdlcContent = pdlcContent.replace(/\{\{REPO_OWNER\}\}/g, () => repoOwner);
355
+ pdlcContent = pdlcContent.replace(/\{\{REPO_NAME\}\}/g, () => repoName);
356
+ if (Object.keys(optionMap).length > 0) {
357
+ pdlcContent = pdlcContent.replace(/\{\{ID_IDEA\}\}/g, () => optionMap['💡 Idea - No move to Exploration directly'] || 'MISSING_ID');
358
+ pdlcContent = pdlcContent.replace(/\{\{ID_EXPLORATION\}\}/g, () => optionMap['🔍 Exploration'] || 'MISSING_ID');
359
+ pdlcContent = pdlcContent.replace(/\{\{ID_BRAINSTORMING\}\}/g, () => optionMap['🧠 Brainstorming'] || 'MISSING_ID');
360
+ pdlcContent = pdlcContent.replace(/\{\{ID_DETAIL\}\}/g, () => optionMap['📐 Detail Solution'] || 'MISSING_ID');
361
+ pdlcContent = pdlcContent.replace(/\{\{ID_APPROVAL\}\}/g, () => optionMap['✅ Approval'] || 'MISSING_ID');
362
+ pdlcContent = pdlcContent.replace(/\{\{ID_DEVELOPMENT\}\}/g, () => optionMap['⚙️ Development'] || 'MISSING_ID');
363
+ pdlcContent = pdlcContent.replace(/\{\{ID_TESTING\}\}/g, () => optionMap['🧪 Testing'] || 'MISSING_ID');
364
+ pdlcContent = pdlcContent.replace(/\{\{ID_CODE_REVIEW_PR\}\}/g, () => optionMap['👁 Code Review / PR'] || 'MISSING_ID');
365
+ pdlcContent = pdlcContent.replace(/\{\{ID_READY_FOR_PRODUCTION\}\}/g,() => optionMap['🚀 Ready for Production']|| 'MISSING_ID');
161
366
  }
162
- if (scopesAfter.length > 0) {
163
- console.log(`\n${green}✅ Token refreshed. Active scopes: ${scopesAfter.join(', ')}${reset}\n`);
367
+ fs.writeFileSync(pdlcDest, pdlcContent);
368
+ if (projectId && statusFieldId && Object.keys(optionMap).length > 0) {
369
+ console.log(`${i18n.pdlc_prefilled}`);
164
370
  } else {
165
- console.log(`\n${green} Token refreshed with 'project' scope.${reset}\n`);
371
+ console.log(`${yellow}⚠️ pdlc.md copied Project IDs not filled (board creation failed). Re-run after fixing token.${reset}`);
166
372
  }
167
373
  }
168
374
 
375
+ // project-automation.yml — substitute IDs
376
+ const paPath = path.join(destTemplates, '.github', 'workflows', 'project-automation.yml');
377
+ if (fs.existsSync(paPath) && Object.keys(optionMap).length > 0) {
378
+ let wfContent = fs.readFileSync(paPath, 'utf8');
379
+ if (statusFieldId) wfContent = wfContent.replace(/\{\{STATUS_FIELD_ID\}\}/g, () => statusFieldId);
380
+ wfContent = wfContent.replace(/\{\{ID_IDEA\}\}/g, () => optionMap['💡 Idea - No move to Exploration directly'] || 'MISSING_ID');
381
+ wfContent = wfContent.replace(/\{\{ID_EXPLORATION\}\}/g, () => optionMap['🔍 Exploration'] || 'MISSING_ID');
382
+ wfContent = wfContent.replace(/\{\{ID_BRAINSTORMING\}\}/g, () => optionMap['🧠 Brainstorming'] || 'MISSING_ID');
383
+ wfContent = wfContent.replace(/\{\{ID_DETAILING\}\}/g, () => optionMap['📐 Detail Solution'] || 'MISSING_ID');
384
+ wfContent = wfContent.replace(/\{\{ID_APPROVAL\}\}/g, () => optionMap['✅ Approval'] || 'MISSING_ID');
385
+ wfContent = wfContent.replace(/\{\{ID_DEVELOPMENT\}\}/g, () => optionMap['⚙️ Development'] || 'MISSING_ID');
386
+ wfContent = wfContent.replace(/\{\{ID_TESTING\}\}/g, () => optionMap['🧪 Testing'] || 'MISSING_ID');
387
+ wfContent = wfContent.replace(/\{\{ID_CODE_REVIEW_PR\}\}/g, () => optionMap['👁 Code Review / PR'] || 'MISSING_ID');
388
+ wfContent = wfContent.replace(/\{\{ID_PRODUCTION\}\}/g, () => optionMap['🚀 Ready for Production']|| 'MISSING_ID');
389
+ fs.writeFileSync(paPath, wfContent);
390
+ }
391
+
392
+ console.log(`${i18n.templates_copied}`);
393
+ }
394
+
395
+ function writeCliContext(targetDir, profile, data) {
396
+ try {
397
+ const contextPath = path.join(targetDir, '.agentic-pdlc', 'cli-context.json');
398
+ fs.mkdirSync(path.join(targetDir, '.agentic-pdlc'), { recursive: true });
399
+ fs.writeFileSync(contextPath, JSON.stringify({ profile, ...data }, null, 2));
400
+ } catch (_) {
401
+ // Non-fatal — agent will ask for the values instead
402
+ }
403
+ }
404
+
405
+ // ─── runFullSetup ─────────────────────────────────────────────────────────────
406
+
407
+ async function runFullSetup() {
408
+ await checkGhAuth();
409
+ await checkAndRefreshProjectScope();
410
+
169
411
  const agentAnswer = await askQuestion(i18n.ask_agent);
170
412
  const agent = agentAnswer.trim().toLowerCase();
171
413
  if (!['claude', 'cursor', 'copilot'].includes(agent)) {
@@ -204,21 +446,7 @@ async function runSetup() {
204
446
  console.log(`\n${yellow}${i18n.starting_setup}${reset}`);
205
447
 
206
448
  // Labels
207
- const labels = [
208
- { name: 'stage:exploration', color: '9b59b6', description: 'Issue is being evaluated' },
209
- { name: 'stage:brainstorming', color: 'e84393', description: 'Proposed approaches awaiting PM gate' },
210
- { name: 'stage:detailing', color: '3498db', description: 'Technical spec is being written' },
211
- { name: 'stage:development', color: 'e67e22', description: 'Agent is implementing the spec' },
212
- { name: 'stage:testing', color: '8e44ad', description: 'Agent is testing the implementation' },
213
- { name: 'spec:approved', color: '0e8a16', description: 'Spec approved — agent can implement' },
214
- { name: 'pr:in-review', color: 'e4e669', description: 'PR awaiting code review' },
215
- { name: 'pr:approved', color: '0e8a16', description: 'PR approved, ready for merge' },
216
- { name: 'architecture-violation', color: 'd93f0b', description: 'Invariant violation detected by CI' },
217
- { name: 'qa:approved', color: '0e8a16', description: 'QA Agent approved the implementation' },
218
- { name: 'qa:needs-work', color: 'd93f0b', description: 'QA Agent found issues' },
219
- { name: 'infra:qa-broken', color: 'F97316', description: 'QA Agent failed to run — manual review required' },
220
- { name: 'jules', color: '5319e7', description: 'Jules AI Agent' }
221
- ];
449
+ const labels = PDLC_LABELS;
222
450
 
223
451
  console.log(`\n${cyan}${i18n.creating_labels}${reset}`);
224
452
  for (const label of labels) {
@@ -341,180 +569,53 @@ async function runSetup() {
341
569
  }
342
570
  }
343
571
 
344
- // Auto-provision PROJECT_PAT for personal repos
572
+ // Auto-provision PROJECT_TOKEN for personal repos
345
573
  let patAutoSet = false;
346
574
  if (projectId && !isOrg) {
347
575
  try {
348
576
  const tokenOut = execFileSync('gh', ['auth', 'token'], { stdio: ['ignore', 'pipe', 'pipe'], encoding: 'utf8' }).trim();
349
577
  if (tokenOut) {
350
- execFileSync('gh', ['secret', 'set', 'PROJECT_PAT', '--body', tokenOut, '--repo', repo], { stdio: ['ignore', 'pipe', 'pipe'] });
578
+ execFileSync('gh', ['secret', 'set', 'PROJECT_TOKEN', '--body', tokenOut, '--repo', repo], { stdio: ['ignore', 'pipe', 'pipe'] });
351
579
  patAutoSet = true;
352
- console.log(`\n${green}✅ PROJECT_PAT secret set automatically (uses your gh OAuth token).${reset}`);
580
+ console.log(`\n${green}✅ PROJECT_TOKEN secret set automatically (uses your gh OAuth token).${reset}`);
353
581
  }
354
582
  } catch (err) {
355
- console.log(`\n${yellow}⚠️ Could not auto-set PROJECT_PAT. Agent will guide manual setup.${reset}`);
583
+ console.log(`\n${yellow}⚠️ Could not auto-set PROJECT_TOKEN. Agent will guide manual setup.${reset}`);
356
584
  }
357
585
  } else if (projectId && isOrg) {
358
- console.log(`\n${yellow}ℹ️ Org repo detected — PROJECT_PAT will require manual setup for security.${reset}`);
586
+ console.log(`\n${yellow}ℹ️ Org repo detected — PROJECT_TOKEN will require manual setup for security.${reset}`);
359
587
  }
360
588
 
361
- // Branch protection require PDLC Stage Gate + QA Gate on default branch
362
- console.log(`\n${cyan}${i18n.configuring_protection}${reset}`);
363
- try {
364
- const defaultBranch = execFileSync('gh', ['api', `repos/${repo}`, '--jq', '.default_branch'],
365
- { stdio: ['ignore', 'pipe', 'ignore'], encoding: 'utf8' }).trim() || 'main';
366
- const protectionPayload = JSON.stringify({
367
- required_status_checks: { strict: false, contexts: ['PDLC Stage Gate', 'QA Gate'] },
368
- enforce_admins: false,
369
- required_pull_request_reviews: null,
370
- restrictions: null
371
- });
372
- execFileSync('gh', ['api', `repos/${repo}/branches/${defaultBranch}/protection`, '--method', 'PUT', '--input', '-'],
373
- { input: protectionPayload, stdio: ['pipe', 'ignore', 'pipe'] });
374
- console.log(` ${green}${i18n.protection_ok}${reset}`);
375
- } catch (_) {
376
- console.log(` ${yellow}${i18n.protection_warn}${reset}`);
589
+ // Set PROJECT_ID as GitHub Actions Variable
590
+ if (projectId) {
591
+ try {
592
+ setActionsVariable(repo, 'PROJECT_ID', projectId);
593
+ console.log(`${green}${i18n.vars_project_id_ok}${reset}`);
594
+ } catch (_) {
595
+ console.log(`${yellow}${i18n.vars_project_id_warn}${projectId}${reset}`);
596
+ }
377
597
  }
378
598
 
379
- console.log(`\n${yellow}${i18n.scaffolding}${reset}`);
380
-
381
- // We copy the templates folder so the agent has the real text logic to replace and rename
382
- const sourceTemplates = path.join(sourceDir, 'templates');
383
- const targetTemplates = path.join(targetDir, '.agentic-pdlc', 'templates');
384
-
385
- if (fs.existsSync(sourceTemplates)) {
386
- copyDirSync(sourceTemplates, targetTemplates);
387
- console.log(`${i18n.templates_copied}`);
388
-
389
- // Substitute values in docs/pdlc.md automatically
390
- const pdlcDest = path.join(targetTemplates, 'docs', 'pdlc.md');
391
- if (fs.existsSync(pdlcDest)) {
392
- let pdlcContent = fs.readFileSync(pdlcDest, 'utf8');
393
-
394
- if (projectId) pdlcContent = pdlcContent.replace(/\{\{PROJECT_ID\}\}/g, () => projectId);
395
- if (statusFieldId) pdlcContent = pdlcContent.replace(/\{\{STATUS_FIELD_ID\}\}/g, () => statusFieldId);
396
- pdlcContent = pdlcContent.replace(/\{\{REPO_OWNER\}\}/g, () => repoOwner);
397
- pdlcContent = pdlcContent.replace(/\{\{REPO_NAME\}\}/g, () => repoName);
398
-
399
- if (Object.keys(optionMap).length > 0) {
400
- pdlcContent = pdlcContent.replace(/\{\{ID_IDEA\}\}/g, optionMap["💡 Idea"] || 'MISSING_ID');
401
- pdlcContent = pdlcContent.replace(/\{\{ID_EXPLORATION\}\}/g, optionMap["🔍 Exploration"] || 'MISSING_ID');
402
- pdlcContent = pdlcContent.replace(/\{\{ID_BRAINSTORMING\}\}/g, optionMap["🧠 Brainstorming"] || 'MISSING_ID');
403
- pdlcContent = pdlcContent.replace(/\{\{ID_DETAIL\}\}/g, optionMap["📐 Detail Solution"] || 'MISSING_ID');
404
- pdlcContent = pdlcContent.replace(/\{\{ID_APPROVAL\}\}/g, optionMap["✅ Approval"] || 'MISSING_ID');
405
- pdlcContent = pdlcContent.replace(/\{\{ID_DEVELOPMENT\}\}/g, optionMap["⚙️ Development"] || 'MISSING_ID');
406
- pdlcContent = pdlcContent.replace(/\{\{ID_TESTING\}\}/g, optionMap["🧪 Testing"] || 'MISSING_ID');
407
- pdlcContent = pdlcContent.replace(/\{\{ID_CODE_REVIEW_PR\}\}/g, optionMap["👁 Code Review / PR"] || 'MISSING_ID');
408
- pdlcContent = pdlcContent.replace(/\{\{ID_READY_FOR_PRODUCTION\}\}/g, optionMap["🚀 Ready for Production"] || 'MISSING_ID');
409
- }
599
+ await setBranchProtection(repo, ['PDLC Stage Gate', 'QA Gate']);
410
600
 
411
- fs.writeFileSync(pdlcDest, pdlcContent);
412
- if (projectId && statusFieldId && Object.keys(optionMap).length > 0) {
413
- console.log(`${i18n.pdlc_prefilled}`);
414
- } else {
415
- console.log(`${yellow}⚠️ pdlc.md copied — Project IDs not filled (board creation failed). Re-run after fixing token.${reset}`);
416
- }
417
- }
418
-
419
- // Pre-fill project-automation.yml with the same IDs so the agent doesn't need to map them
420
- const workflowAutomationPath = path.join(targetTemplates, '.github', 'workflows', 'project-automation.yml');
421
- if (fs.existsSync(workflowAutomationPath)) {
422
- let wfContent = fs.readFileSync(workflowAutomationPath, 'utf8');
423
- if (projectId) wfContent = wfContent.replace(/\{\{PROJECT_ID\}\}/g, () => projectId);
424
- if (statusFieldId) wfContent = wfContent.replace(/\{\{STATUS_FIELD_ID\}\}/g, () => statusFieldId);
425
- if (Object.keys(optionMap).length > 0) {
426
- wfContent = wfContent.replace(/\{\{ID_IDEA\}\}/g, optionMap["💡 Idea"] || 'MISSING_ID');
427
- wfContent = wfContent.replace(/\{\{ID_EXPLORATION\}\}/g, () => optionMap["🔍 Exploration"] || 'MISSING_ID');
428
- wfContent = wfContent.replace(/\{\{ID_BRAINSTORMING\}\}/g, () => optionMap["🧠 Brainstorming"] || 'MISSING_ID');
429
- wfContent = wfContent.replace(/\{\{ID_DETAILING\}\}/g, () => optionMap["📐 Detail Solution"] || 'MISSING_ID');
430
- wfContent = wfContent.replace(/\{\{ID_APPROVAL\}\}/g, () => optionMap["✅ Approval"] || 'MISSING_ID');
431
- wfContent = wfContent.replace(/\{\{ID_DEVELOPMENT\}\}/g, () => optionMap["⚙️ Development"] || 'MISSING_ID');
432
- wfContent = wfContent.replace(/\{\{ID_TESTING\}\}/g, () => optionMap["🧪 Testing"] || 'MISSING_ID');
433
- wfContent = wfContent.replace(/\{\{ID_CODE_REVIEW_PR\}\}/g, () => optionMap["👁 Code Review / PR"] || 'MISSING_ID');
434
- wfContent = wfContent.replace(/\{\{ID_PRODUCTION\}\}/g, () => optionMap["🚀 Ready for Production"] || 'MISSING_ID');
435
- }
436
- fs.writeFileSync(workflowAutomationPath, wfContent);
437
- }
438
- }
601
+ console.log(`\n${yellow}${i18n.scaffolding}${reset}`);
602
+ scaffoldFullTemplates(sourceDir, targetDir, projectId, statusFieldId, optionMap, repoOwner, repoName);
439
603
 
440
604
  // Write CLI context for the agent to consume in Setup Mode
441
- try {
442
- const cliContextPath = path.join(targetDir, '.agentic-pdlc', 'cli-context.json');
443
- const boardUrl = projectNumber ? buildBoardUrl(repoOwner, projectNumber, isOrg) : null;
444
- fs.writeFileSync(cliContextPath, JSON.stringify({
445
- projectName,
446
- repoOwner,
447
- repoName,
448
- projectNumber,
449
- isOrg,
450
- boardUrl,
451
- patAutoSet
452
- }, null, 2));
453
- } catch (err) {
454
- // Non-fatal — agent will ask for the values instead
455
- }
605
+ const boardUrl = projectNumber ? buildBoardUrl(repoOwner, projectNumber, isOrg) : null;
606
+ writeCliContext(targetDir, 'full', {
607
+ projectName,
608
+ repoOwner,
609
+ repoName,
610
+ projectNumber,
611
+ isOrg,
612
+ boardUrl,
613
+ patAutoSet
614
+ });
456
615
 
457
- // Install PDLC stage gate hook (all agents)
458
- const hookSrc = path.join(sourceDir, 'adapters', 'hooks', 'pdlc-stage-gate.sh');
459
- const hookDir = path.join(targetDir, '.agentic-pdlc', 'hooks');
460
- const hookDest = path.join(hookDir, 'pdlc-stage-gate.sh');
461
- if (fs.existsSync(hookSrc)) {
462
- fs.mkdirSync(hookDir, { recursive: true });
463
- fs.copyFileSync(hookSrc, hookDest);
464
- fs.chmodSync(hookDest, '755');
465
- }
466
- const claudeSettingsDir = path.join(targetDir, '.claude');
467
- const claudeSettingsPath = path.join(claudeSettingsDir, 'settings.json');
468
- if (!fs.existsSync(claudeSettingsPath)) {
469
- fs.mkdirSync(claudeSettingsDir, { recursive: true });
470
- fs.writeFileSync(claudeSettingsPath, JSON.stringify({
471
- hooks: {
472
- PreToolUse: [{
473
- matcher: 'Bash',
474
- hooks: [{ type: 'command', command: 'bash .agentic-pdlc/hooks/pdlc-stage-gate.sh' }]
475
- }]
476
- }
477
- }, null, 2) + '\n');
478
- }
616
+ installHook(sourceDir, targetDir);
479
617
 
480
- // Handle the specific setup instructions target
481
- const claudeSetupSrc = path.join(sourceDir, 'adapters', 'claude-code', 'skill.md');
482
- const cursorSetupSrc = path.join(sourceDir, 'adapters', 'cursor', 'rules.md');
483
-
484
- if (agent === 'claude') {
485
- if (fs.existsSync(claudeSetupSrc)) {
486
- const dest = path.join(targetDir, '.agentic-setup.md');
487
- fs.copyFileSync(claudeSetupSrc, dest);
488
- console.log(`${i18n.setup_written}`);
489
- printSetupDone();
490
- } else {
491
- console.error(`${i18n.missing_claude}${claudeSetupSrc}`);
492
- }
493
- } else if (agent === 'cursor') {
494
- if (fs.existsSync(cursorSetupSrc)) {
495
- const dest = path.join(targetDir, '.cursorrules');
496
- fs.copyFileSync(cursorSetupSrc, dest);
497
- console.log(`${i18n.cursor_rules_written}`);
498
-
499
- if (fs.existsSync(claudeSetupSrc)) {
500
- const setupDest = path.join(targetDir, '.agentic-setup.md');
501
- fs.copyFileSync(claudeSetupSrc, setupDest);
502
- console.log(`${i18n.setup_written}`);
503
- }
504
- printSetupDone();
505
- } else {
506
- console.error(`${i18n.missing_claude}${cursorSetupSrc}`);
507
- }
508
- } else {
509
- if (fs.existsSync(claudeSetupSrc)) {
510
- const dest = path.join(targetDir, '.agentic-setup.md');
511
- fs.copyFileSync(claudeSetupSrc, dest);
512
- console.log(`${i18n.setup_written}`);
513
- printSetupDone();
514
- } else {
515
- console.error(`${i18n.missing_claude}${claudeSetupSrc}`);
516
- }
517
- }
618
+ copyAdapterFiles(agent, sourceDir, targetDir);
518
619
 
519
620
  rl.close();
520
621
  }
@@ -617,6 +718,13 @@ async function runUpdate() {
617
718
  process.exit(1);
618
719
  }
619
720
 
721
+ const ctx = JSON.parse(fs.readFileSync(contextPath, 'utf8'));
722
+ if ((ctx.profile || 'full') === 'lite') {
723
+ console.log(`\n${yellow}⚠️ Lite install detected. Run --upgrade-to-agentic to add the full board machine first.${reset}\n`);
724
+ rl.close();
725
+ return;
726
+ }
727
+
620
728
  const state = detectAgentState(targetDir);
621
729
  const sep = '─'.repeat(55);
622
730
 
@@ -690,6 +798,24 @@ async function runUpdate() {
690
798
  }
691
799
  }
692
800
 
801
+ // Upgrade lite → full agentic profile (extend CLAUDE.md, do not replace)
802
+ const claudeMdPath = path.join(targetDir, 'CLAUDE.md');
803
+ const fullClaudeSrc = path.join(sourceDir, 'templates', 'full', 'CLAUDE.md');
804
+ if (fs.existsSync(claudeMdPath) && fs.existsSync(fullClaudeSrc)) {
805
+ const existing = fs.readFileSync(claudeMdPath, 'utf8');
806
+ if (!existing.includes('<!-- agentic-full -->')) {
807
+ console.log(`\n${cyan}— Agentic Profile Upgrade —${reset}`);
808
+ const upgradeAnswer = (await askQuestion(' Extend CLAUDE.md with the full multi-agent pipeline rulebook? (Y/n): ')).trim().toLowerCase();
809
+ if (!['n', 'no', 'não', 'nao'].includes(upgradeAnswer)) {
810
+ const extension = fs.readFileSync(fullClaudeSrc, 'utf8');
811
+ fs.writeFileSync(claudeMdPath, existing.trimEnd() + '\n\n' + extension + '\n');
812
+ results.push('✅ CLAUDE.md extended with full agentic profile');
813
+ } else {
814
+ results.push('⏭ Agentic profile upgrade — skipped');
815
+ }
816
+ }
817
+ }
818
+
693
819
  console.log(`\n${cyan}${sep}${reset}`);
694
820
  for (const r of results) console.log(` ${r}`);
695
821
  console.log(`${cyan}${sep}${reset}\n`);
@@ -697,11 +823,279 @@ async function runUpdate() {
697
823
  rl.close();
698
824
  }
699
825
 
826
+ // ─── resolveMode ──────────────────────────────────────────────────────────────
827
+
828
+ function resolveMode(args) {
829
+ if (args.includes('--update')) return 'update';
830
+ if (args.includes('--upgrade-to-agentic')) return 'upgrade';
831
+ if (args.includes('--agentic')) return 'full';
832
+ return 'lite';
833
+ }
834
+
835
+ // Export for testing
836
+ if (typeof module !== 'undefined') module.exports = { resolveMode, setActionsVariable, scaffoldLiteTemplates, scaffoldFullTemplates };
837
+
838
+ // ─── runLiteSetup ─────────────────────────────────────────────────────────────
839
+
840
+ async function runLiteSetup() {
841
+ await checkGhAuth();
842
+
843
+ const agentAnswer = await askQuestion(i18n.ask_agent);
844
+ const agent = agentAnswer.trim().toLowerCase();
845
+ if (!['claude', 'cursor', 'copilot'].includes(agent)) {
846
+ console.log(t(
847
+ `ℹ️ Generating Universal Setup for '${agent}' (Compatible with any Markdown-reading agent).`,
848
+ `ℹ️ Gerando Setup Universal para '${agent}' (Compatível com qualquer agente que leia Markdown).`,
849
+ `ℹ️ Generando Setup Universal para '${agent}' (Compatible con cualquier agente que lea Markdown).`
850
+ ));
851
+ }
852
+
853
+ let repoOwner, repoName, repo;
854
+ while (true) {
855
+ let repoUrl = (await askQuestion(i18n.ask_repo)).trim();
856
+ if (repoUrl.endsWith('/')) repoUrl = repoUrl.slice(0, -1);
857
+ if (repoUrl.endsWith('.git')) repoUrl = repoUrl.slice(0, -4);
858
+ const repoParts = repoUrl.split('/');
859
+ if (repoParts.length >= 2) {
860
+ repoOwner = repoParts[repoParts.length - 2];
861
+ repoName = repoParts[repoParts.length - 1];
862
+ repo = `${repoOwner}/${repoName}`;
863
+ break;
864
+ }
865
+ console.log(`${red}${i18n.invalid_repo}${reset}`);
866
+ }
867
+
868
+ let isOrg = false;
869
+ try {
870
+ const ownerType = execFileSync('gh', ['api', `repos/${repo}`, '--jq', '.owner.type'],
871
+ { stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim();
872
+ isOrg = ownerType === 'Organization';
873
+ } catch (_) {}
874
+
875
+ console.log(`\n${yellow}${i18n.starting_setup}${reset}`);
876
+
877
+ installHook(sourceDir, targetDir);
878
+
879
+ console.log(`\n${yellow}${i18n.scaffolding}${reset}`);
880
+ scaffoldLiteTemplates(sourceDir, targetDir);
881
+ console.log(`${i18n.templates_copied}`);
882
+
883
+ await setBranchProtection(repo, ['PDLC Stage Gate']);
884
+
885
+ writeCliContext(targetDir, 'lite', {
886
+ repoOwner,
887
+ repoName,
888
+ projectNumber: null,
889
+ isOrg,
890
+ boardUrl: null,
891
+ patAutoSet: false
892
+ });
893
+
894
+ copyAdapterFiles(agent, sourceDir, targetDir);
895
+ console.log(`${cyan}${i18n.upgrade_hint}${reset}`);
896
+
897
+ rl.close();
898
+ }
899
+
900
+ async function runUpgradeToAgentic() {
901
+ const contextPath = path.join(targetDir, '.agentic-pdlc', 'cli-context.json');
902
+ if (!fs.existsSync(contextPath)) {
903
+ console.error(`\n${red}${i18n.update_no_context}${reset}\n`);
904
+ rl.close();
905
+ process.exit(1);
906
+ }
907
+
908
+ const ctx = JSON.parse(fs.readFileSync(contextPath, 'utf8'));
909
+ if ((ctx.profile || 'full') === 'full') {
910
+ console.log(`\n${green}✅ Already running full profile. Nothing to upgrade.${reset}\n`);
911
+ rl.close();
912
+ return;
913
+ }
914
+
915
+ await checkGhAuth();
916
+ await checkAndRefreshProjectScope();
917
+
918
+ const { repoOwner, repoName } = ctx;
919
+ const repo = `${repoOwner}/${repoName}`;
920
+
921
+ const askProjectName = t(
922
+ `What is the project name for the board? (default: ${repoName.toUpperCase()}): `,
923
+ `Qual o nome do projeto em que o board será configurado? (padrão: ${repoName.toUpperCase()}): `,
924
+ `¿Cuál es el nombre del proyecto en el que se configurará el board? (por defecto: ${repoName.toUpperCase()}): `
925
+ );
926
+ const projectNameAnswer = await askQuestion(askProjectName);
927
+ const projectName = projectNameAnswer.trim() ? projectNameAnswer.trim().toUpperCase() : repoName.toUpperCase();
928
+ const boardName = `BOARD - ${projectName}`;
929
+
930
+ let isOrg = ctx.isOrg || false;
931
+ try {
932
+ const ownerType = execFileSync('gh', ['api', `repos/${repo}`, '--jq', '.owner.type'],
933
+ { stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim();
934
+ isOrg = ownerType === 'Organization';
935
+ } catch (_) {}
936
+
937
+ console.log(`\n${yellow}${i18n.starting_setup}${reset}`);
938
+
939
+ // Labels
940
+ const labels = PDLC_LABELS;
941
+
942
+ console.log(`\n${cyan}${i18n.creating_labels}${reset}`);
943
+ for (const label of labels) {
944
+ try {
945
+ execFileSync('gh', ['label', 'create', label.name, '--color', label.color, '--description', label.description, '--repo', repo, '--force'], { stdio: 'ignore' });
946
+ console.log(` ${i18n.label_ok}${label.name}`);
947
+ } catch (err) {
948
+ console.log(` ${i18n.label_warn}${label.name}`);
949
+ }
950
+ }
951
+
952
+ // Board
953
+ console.log(`\n${cyan}${i18n.creating_project}${reset}`);
954
+ let ownerId, projectId, projectNumber;
955
+ try {
956
+ if (isOrg) {
957
+ ownerId = execFileSync('gh', ['api', 'graphql', '-f', 'query=query($login: String!) { organization(login: $login) { id } }', '-f', `login=${repoOwner}`, '--jq', '.data.organization.id'],
958
+ { stdio: ['ignore', 'pipe', 'pipe'] }).toString().trim();
959
+ } else {
960
+ ownerId = execFileSync('gh', ['api', 'graphql', '-f', 'query={ viewer { id } }', '--jq', '.data.viewer.id'],
961
+ { stdio: ['ignore', 'pipe', 'pipe'] }).toString().trim();
962
+ }
963
+
964
+ const raw = execFileSync('gh', ['api', 'graphql', '-f',
965
+ 'query=mutation($owner: ID!, $title: String!) { createProjectV2(input: {ownerId: $owner, title: $title}) { projectV2 { id number } } }',
966
+ '-f', `owner=${ownerId}`, '-f', `title=${boardName}`],
967
+ { stdio: ['ignore', 'pipe', 'pipe'] }).toString().trim();
968
+ const resp = raw ? JSON.parse(raw) : null;
969
+ if (resp?.errors) throw new Error(resp.errors.map(e => e.message).join('; '));
970
+ const pData = resp?.data?.createProjectV2?.projectV2;
971
+ projectId = pData?.id;
972
+ projectNumber = pData?.number;
973
+ console.log(` ${i18n.project_ok}${projectId})`);
974
+
975
+ try {
976
+ const repoNodeId = execFileSync('gh', ['api', `repos/${repo}`, '--jq', '.node_id'],
977
+ { stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim();
978
+ execFileSync('gh', ['api', 'graphql', '-f',
979
+ 'query=mutation($projectId: ID!, $repositoryId: ID!) { linkProjectV2ToRepository(input: {projectId: $projectId, repositoryId: $repositoryId}) { repository { name } } }',
980
+ '-f', `projectId=${projectId}`, '-f', `repositoryId=${repoNodeId}`],
981
+ { stdio: 'ignore' });
982
+ console.log(` ${i18n.link_project_ok}`);
983
+ } catch (_) {
984
+ console.log(` ${i18n.link_project_warn}`);
985
+ }
986
+ } catch (err) {
987
+ console.log(` ${i18n.project_err}${err.message}`);
988
+ }
989
+
990
+ let statusFieldId;
991
+ let optionMap = {};
992
+
993
+ if (projectId) {
994
+ console.log(` ${cyan}${i18n.config_columns}${reset}`);
995
+ try {
996
+ statusFieldId = execFileSync('gh', ['api', 'graphql', '-f',
997
+ 'query=query($projectId: ID!) { node(id: $projectId) { ... on ProjectV2 { fields(first: 20) { nodes { ... on ProjectV2SingleSelectField { id name } } } } } }',
998
+ '-f', `projectId=${projectId}`, '--jq', '.data.node.fields.nodes[] | select(.name == "Status") | .id'
999
+ ]).toString().trim();
1000
+
1001
+ if (statusFieldId) {
1002
+ const columns = [
1003
+ { name: '💡 Idea - No move to Exploration directly', description: 'Just tell your agent to work on issue #XX', color: 'GRAY' },
1004
+ { name: '🔍 Exploration', description: 'AI is analyzing code and context', color: 'PURPLE' },
1005
+ { name: '🧠 Brainstorming', description: 'AI proposed approaches and trade-offs', color: 'PINK' },
1006
+ { name: '📐 Detail Solution', description: 'AI is writing the technical spec', color: 'BLUE' },
1007
+ { name: '✅ Approval', description: 'Spec ready, awaiting `spec:approved` label', color: 'GREEN' },
1008
+ { name: '⚙️ Development', description: 'AI implementing the spec', color: 'ORANGE' },
1009
+ { name: '🧪 Testing', description: 'QA testing and CI pipeline checks', color: 'RED' },
1010
+ { name: '👁 Code Review / PR',description: 'PR opened, awaiting your review', color: 'YELLOW' },
1011
+ { name: '🚀 Ready for Production', description: 'Merged and ready for production', color: 'GREEN' }
1012
+ ];
1013
+
1014
+ const queryPayload = JSON.stringify({
1015
+ query: `mutation($fieldId: ID!, $options: [ProjectV2SingleSelectFieldOptionInput!]) {
1016
+ updateProjectV2Field(input: { fieldId: $fieldId, singleSelectOptions: $options }) {
1017
+ projectV2Field { ... on ProjectV2SingleSelectField { options { id name } } }
1018
+ }
1019
+ }`,
1020
+ variables: { fieldId: statusFieldId, options: columns }
1021
+ });
1022
+
1023
+ const updateOutput = execFileSync('gh', ['api', 'graphql', '--input', '-'],
1024
+ { input: queryPayload }).toString().trim();
1025
+ const jsonResponse = updateOutput ? JSON.parse(updateOutput) : null;
1026
+ const returnedOptions = jsonResponse?.data?.updateProjectV2Field?.projectV2Field?.options || [];
1027
+ for (const opt of returnedOptions) optionMap[opt.name] = opt.id;
1028
+ console.log(` ${i18n.columns_ok}`);
1029
+ }
1030
+ } catch (_) {
1031
+ console.log(` ${i18n.columns_warn}`);
1032
+ }
1033
+ }
1034
+
1035
+ // Auto-provision PROJECT_TOKEN for personal repos
1036
+ let patAutoSet = false;
1037
+ if (projectId && !isOrg) {
1038
+ try {
1039
+ const tokenOut = execFileSync('gh', ['auth', 'token'],
1040
+ { stdio: ['ignore', 'pipe', 'pipe'], encoding: 'utf8' }).trim();
1041
+ if (tokenOut) {
1042
+ execFileSync('gh', ['secret', 'set', 'PROJECT_TOKEN', '--body', tokenOut, '--repo', repo],
1043
+ { stdio: ['ignore', 'pipe', 'pipe'] });
1044
+ patAutoSet = true;
1045
+ console.log(`\n${green}✅ PROJECT_TOKEN secret set automatically (uses your gh OAuth token).${reset}`);
1046
+ }
1047
+ } catch (_) {
1048
+ console.log(`\n${yellow}⚠️ Could not auto-set PROJECT_TOKEN. Agent will guide manual setup.${reset}`);
1049
+ }
1050
+ }
1051
+
1052
+ // Set PROJECT_ID as GitHub Actions Variable
1053
+ if (projectId) {
1054
+ try {
1055
+ setActionsVariable(repo, 'PROJECT_ID', projectId);
1056
+ console.log(`${green}${i18n.vars_project_id_ok}${reset}`);
1057
+ } catch (_) {
1058
+ console.log(`${yellow}${i18n.vars_project_id_warn}${projectId}${reset}`);
1059
+ }
1060
+ }
1061
+
1062
+ console.log(`\n${yellow}${i18n.scaffolding}${reset}`);
1063
+ scaffoldFullTemplates(sourceDir, targetDir, projectId, statusFieldId, optionMap, repoOwner, repoName);
1064
+
1065
+ await setBranchProtection(repo, ['PDLC Stage Gate', 'QA Gate']);
1066
+
1067
+ const boardUrl = projectNumber ? buildBoardUrl(repoOwner, projectNumber, isOrg) : null;
1068
+ writeCliContext(targetDir, 'full', {
1069
+ projectName,
1070
+ repoOwner,
1071
+ repoName,
1072
+ projectNumber,
1073
+ isOrg,
1074
+ boardUrl,
1075
+ patAutoSet
1076
+ });
1077
+
1078
+ const line1 = t('🎉 Upgrade complete! Board:', '🎉 Upgrade concluído! Board:', '🎉 ¡Actualización completada! Board:');
1079
+ console.log(`\n${green}${line1} ${boardUrl || '(board creation failed)'}${reset}\n`);
1080
+
1081
+ rl.close();
1082
+ }
1083
+
700
1084
  // ─── Entry point ──────────────────────────────────────────────────────────────
701
1085
 
702
- const args = process.argv.slice(2);
703
- if (args.includes('--update')) {
704
- runUpdate().catch(err => { console.error(err.message); rl.close(); process.exit(1); });
705
- } else {
706
- runSetup().catch(err => { console.error(err.message); rl.close(); process.exit(1); });
1086
+ if (require.main === module) {
1087
+ console.log(`${cyan}================================================================${reset}`);
1088
+ console.log(`${cyan}${i18n.welcome}${reset}`);
1089
+ console.log(`${cyan}================================================================${reset}\n`);
1090
+
1091
+ const args = process.argv.slice(2);
1092
+ const mode = resolveMode(args);
1093
+
1094
+ const handler =
1095
+ mode === 'update' ? runUpdate :
1096
+ mode === 'upgrade' ? runUpgradeToAgentic :
1097
+ mode === 'full' ? runFullSetup :
1098
+ runLiteSetup;
1099
+
1100
+ handler().catch(err => { console.error(err.message); rl.close(); process.exit(1); });
707
1101
  }