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
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
 
@@ -41,10 +41,10 @@ const i18n = {
41
41
  invalid_repo: t('❌ Invalid repository URL. Expected format: https://github.com/OWNER/REPO', '❌ URL de repositório inválida. Formato esperado: https://github.com/OWNER/REPO', '❌ URL de repositorio inválida. Formato esperado: https://github.com/OWNER/REPO'),
42
42
  ask_org: t('Does this repository belong to a personal User account (e.g., github.com/rafaeltcosta86) or an Organization (e.g., github.com/google-labs)? (user/org): ', 'Esse repositório pertence a um Usuário pessoal (ex: github.com/rafaeltcosta86) ou a uma Organização (ex: github.com/google-labs)? (user/org): ', '¿Este repositorio pertenece a un Usuario personal (ej: github.com/rafaeltcosta86) o a una Organización (ej: github.com/google-labs)? (user/org): '),
43
43
  starting_setup: t('Starting automated repository setup...', 'Iniciando o setup automatizado do repositório...', 'Iniciando la configuración automatizada del repositorio...'),
44
- creating_labels: t('[1/2] Creating repository labels...', '[1/2] Criando labels no repositório...', '[1/2] Creando etiquetas (labels) en el repositorio...'),
44
+ creating_labels: t('[1/3] Creating repository labels...', '[1/3] Criando labels no repositório...', '[1/3] Creando etiquetas (labels) en el repositorio...'),
45
45
  label_ok: t('✅ Label created: ', '✅ Label criada: ', '✅ Etiqueta creada: '),
46
46
  label_warn: t('⚠️ Failed to create label (might already exist): ', '⚠️ Falha ao criar label (talvez já exista): ', '⚠️ Fallo al crear etiqueta (quizás ya exista): '),
47
- creating_project: t('[2/2] Creating Project V2 Board...', '[2/2] Criando Project V2 Board...', '[2/2] Creando Project V2 Board...'),
47
+ creating_project: t('[2/3] Creating Project V2 Board...', '[2/3] Criando Project V2 Board...', '[2/3] Creando Project V2 Board...'),
48
48
  project_ok: t('✅ Project created (ID: ', '✅ Projeto criado (ID: ', '✅ Proyecto creado (ID: '),
49
49
  project_err: t('❌ Failed to create project. Error: ', '❌ Falha ao criar o projeto. Erro: ', '❌ Fallo al crear el proyecto. Error: '),
50
50
  link_project_ok: t('✅ Project linked to repository.', '✅ Projeto vinculado ao repositório.', '✅ Proyecto vinculado al repositorio.'),
@@ -70,8 +70,16 @@ const i18n = {
70
70
  update_jules_ask_handle: t(' Agent handle (e.g. @my-agent): ', ' Handle do agente (ex: @meu-agente): ', ' Handle del agente (ej: @mi-agente): '),
71
71
  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
72
  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): '),
73
+ 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) —'),
74
+ 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
+ configuring_protection: t('[3/3] Configuring branch protection...', '[3/3] Configurando proteção de branch...', '[3/3] Configurando protección de rama...'),
76
+ 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
+ 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"'),
78
+ issue_templates_copied: t(
79
+ '✅ Issue templates copied to .github/ISSUE_TEMPLATE/',
80
+ '✅ Issue templates copiados para .github/ISSUE_TEMPLATE/',
81
+ '✅ Issue templates copiados a .github/ISSUE_TEMPLATE/'
82
+ ),
75
83
  };
76
84
 
77
85
  const cyan = '\x1b[36m';
@@ -80,9 +88,21 @@ const green = '\x1b[32m';
80
88
  const yellow = '\x1b[33m';
81
89
  const red = '\x1b[31m';
82
90
 
83
- console.log(`${cyan}================================================================${reset}`);
84
- console.log(`${cyan}${i18n.welcome}${reset}`);
85
- console.log(`${cyan}================================================================${reset}\n`);
91
+ const PDLC_LABELS = [
92
+ { name: 'stage:exploration', color: '9b59b6', description: 'Issue is being evaluated' },
93
+ { name: 'stage:brainstorming', color: 'e84393', description: 'Proposed approaches awaiting PM gate' },
94
+ { name: 'stage:detailing', color: '3498db', description: 'Technical spec is being written' },
95
+ { name: 'stage:development', color: 'e67e22', description: 'Agent is implementing the spec' },
96
+ { name: 'stage:testing', color: '8e44ad', description: 'Agent is testing the implementation' },
97
+ { name: 'spec:approved', color: '0e8a16', description: 'Spec approved — agent can implement' },
98
+ { name: 'pr:in-review', color: 'e4e669', description: 'PR awaiting code review' },
99
+ { name: 'pr:approved', color: '0e8a16', description: 'PR approved, ready for merge' },
100
+ { name: 'architecture-violation', color: 'd93f0b', description: 'Invariant violation detected by CI' },
101
+ { name: 'qa:approved', color: '0e8a16', description: 'QA Agent approved the implementation' },
102
+ { name: 'qa:needs-work', color: 'd93f0b', description: 'QA Agent found issues' },
103
+ { name: 'infra:qa-broken', color: 'F97316', description: 'QA Agent failed to run — manual review required' },
104
+ { name: 'jules', color: '5319e7', description: 'Jules AI Agent' }
105
+ ];
86
106
 
87
107
  function buildBoardUrl(repoOwner, projectNumber, isOrg) {
88
108
  const segment = isOrg ? 'orgs' : 'users';
@@ -115,7 +135,9 @@ function printSetupDone() {
115
135
  console.log(`${green}${sep}${reset}\n`);
116
136
  }
117
137
 
118
- async function runSetup() {
138
+ // ─── Shared helper functions ──────────────────────────────────────────────────
139
+
140
+ async function checkGhAuth() {
119
141
  console.log(`${yellow}${i18n.checking_gh}${reset}`);
120
142
  try {
121
143
  execSync('gh auth status', { stdio: 'ignore' });
@@ -123,46 +145,236 @@ async function runSetup() {
123
145
  } catch (error) {
124
146
  console.error(`${red}${i18n.gh_error}${reset}`);
125
147
  console.error(`${i18n.gh_install}`);
148
+ rl.close();
126
149
  process.exit(1);
127
150
  }
151
+ }
128
152
 
129
- function getScopes() {
130
- try {
131
- const out = execFileSync('gh', ['api', 'user', '-i'], { stdio: ['ignore', 'pipe', 'pipe'], encoding: 'utf8' });
132
- const line = out.split('\n').find(l => l.toLowerCase().startsWith('x-oauth-scopes:'));
133
- return line ? line.split(':').slice(1).join(':').split(',').map(s => s.trim()) : [];
134
- } catch (e) {
135
- return [];
136
- }
153
+ function getScopes() {
154
+ try {
155
+ const out = execFileSync('gh', ['api', 'user', '-i'], { stdio: ['ignore', 'pipe', 'pipe'], encoding: 'utf8' });
156
+ const line = out.split('\n').find(l => l.toLowerCase().startsWith('x-oauth-scopes:'));
157
+ return line ? line.split(':').slice(1).join(':').split(',').map(s => s.trim()) : [];
158
+ } catch (e) {
159
+ return [];
137
160
  }
161
+ }
138
162
 
163
+ async function checkAndRefreshProjectScope() {
139
164
  const scopesBefore = getScopes();
140
- if (scopesBefore.length > 0 && !scopesBefore.includes('project')) {
141
- console.log(`${yellow}⚠️ Token missing 'project' scope — required for GitHub Projects board.${reset}`);
142
- console.log(`${yellow} Refreshing token now (browser may open)...${reset}\n`);
143
- try {
144
- execSync('gh auth refresh -h github.com -s project', { stdio: 'inherit' });
145
- } catch (e) {
146
- console.log(`${red} Token refresh failed. Run manually: gh auth refresh -h github.com -s project${reset}`);
147
- rl.close();
148
- process.exit(1);
165
+ if (scopesBefore.length === 0 || scopesBefore.includes('project')) return;
166
+
167
+ console.log(`${yellow}⚠️ Token missing 'project' scope required for GitHub Projects board.${reset}`);
168
+ console.log(`${yellow} Refreshing token now (browser may open)...${reset}\n`);
169
+ try {
170
+ execSync('gh auth refresh -h github.com -s project', { stdio: 'inherit' });
171
+ } catch (e) {
172
+ console.log(`${red}❌ Token refresh failed. Run manually: gh auth refresh -h github.com -s project${reset}`);
173
+ rl.close();
174
+ process.exit(1);
175
+ }
176
+ const scopesAfter = getScopes();
177
+ if (scopesAfter.length > 0 && !scopesAfter.includes('project')) {
178
+ console.log(`\n${red}❌ 'project' scope still missing after refresh.${reset}`);
179
+ console.log(`${yellow} Active scopes: ${scopesAfter.join(', ')}${reset}`);
180
+ console.log(`${yellow} Try manually: gh auth refresh -h github.com -s project${reset}`);
181
+ rl.close();
182
+ process.exit(1);
183
+ }
184
+ if (scopesAfter.length > 0) {
185
+ console.log(`\n${green}✅ Token refreshed. Active scopes: ${scopesAfter.join(', ')}${reset}\n`);
186
+ } else {
187
+ console.log(`\n${green}✅ Token refreshed with 'project' scope.${reset}\n`);
188
+ }
189
+ }
190
+
191
+ function installHook(sourceDir, targetDir) {
192
+ const hookSrc = path.join(sourceDir, 'adapters', 'hooks', 'pdlc-stage-gate.sh');
193
+ const hookDir = path.join(targetDir, '.agentic-pdlc', 'hooks');
194
+ const hookDest = path.join(hookDir, 'pdlc-stage-gate.sh');
195
+ if (!fs.existsSync(hookSrc)) return;
196
+
197
+ fs.mkdirSync(hookDir, { recursive: true });
198
+ fs.copyFileSync(hookSrc, hookDest);
199
+ fs.chmodSync(hookDest, '755');
200
+
201
+ const settingsDir = path.join(targetDir, '.claude');
202
+ const settingsPath = path.join(settingsDir, 'settings.json');
203
+ if (!fs.existsSync(settingsPath)) {
204
+ fs.mkdirSync(settingsDir, { recursive: true });
205
+ fs.writeFileSync(settingsPath, JSON.stringify({
206
+ hooks: {
207
+ PreToolUse: [{
208
+ matcher: 'Bash',
209
+ hooks: [{ type: 'command', command: 'bash .agentic-pdlc/hooks/pdlc-stage-gate.sh' }]
210
+ }]
211
+ }
212
+ }, null, 2) + '\n');
213
+ }
214
+ }
215
+
216
+ async function setBranchProtection(repo, requiredChecks) {
217
+ console.log(`\n${cyan}${i18n.configuring_protection}${reset}`);
218
+ try {
219
+ const defaultBranch = execFileSync(
220
+ 'gh', ['api', `repos/${repo}`, '--jq', '.default_branch'],
221
+ { stdio: ['ignore', 'pipe', 'ignore'], encoding: 'utf8' }
222
+ ).trim() || 'main';
223
+
224
+ const protectionPayload = JSON.stringify({
225
+ required_status_checks: { strict: false, contexts: requiredChecks },
226
+ enforce_admins: false,
227
+ required_pull_request_reviews: null,
228
+ restrictions: null
229
+ });
230
+
231
+ execFileSync(
232
+ 'gh',
233
+ ['api', `repos/${repo}/branches/${defaultBranch}/protection`, '--method', 'PUT', '--input', '-'],
234
+ { input: protectionPayload, stdio: ['pipe', 'ignore', 'pipe'] }
235
+ );
236
+ console.log(` ${green}${i18n.protection_ok}${reset}`);
237
+ } catch (_) {
238
+ console.log(` ${yellow}${i18n.protection_warn}${reset}`);
239
+ }
240
+ }
241
+
242
+ function copyAdapterFiles(agent, sourceDir, targetDir) {
243
+ const claudeSetupSrc = path.join(sourceDir, 'adapters', 'claude-code', 'skill.md');
244
+ const cursorSetupSrc = path.join(sourceDir, 'adapters', 'cursor', 'rules.md');
245
+
246
+ if (agent === 'cursor') {
247
+ if (fs.existsSync(cursorSetupSrc)) {
248
+ fs.copyFileSync(cursorSetupSrc, path.join(targetDir, '.cursorrules'));
249
+ console.log(`${i18n.cursor_rules_written}`);
149
250
  }
150
- const scopesAfter = getScopes();
151
- if (scopesAfter.length > 0 && !scopesAfter.includes('project')) {
152
- console.log(`\n${red}❌ 'project' scope still missing after refresh.${reset}`);
153
- console.log(`${yellow} Active scopes: ${scopesAfter.join(', ')}${reset}`);
154
- console.log(`${yellow} Note: gh uses OAuth tokens — visible at github.com/settings/applications, not /settings/tokens${reset}`);
155
- console.log(`${yellow} Try manually: gh auth refresh -h github.com -s project${reset}`);
156
- rl.close();
157
- process.exit(1);
251
+ }
252
+ if (fs.existsSync(claudeSetupSrc)) {
253
+ fs.copyFileSync(claudeSetupSrc, path.join(targetDir, '.agentic-setup.md'));
254
+ console.log(`${i18n.setup_written}`);
255
+ printSetupDone();
256
+ } else {
257
+ console.error(`${i18n.missing_claude}${claudeSetupSrc}`);
258
+ }
259
+ }
260
+
261
+ function scaffoldLiteTemplates(sourceDir, targetDir) {
262
+ const destTemplates = path.join(targetDir, '.agentic-pdlc', 'templates');
263
+ fs.mkdirSync(destTemplates, { recursive: true });
264
+
265
+ // CLAUDE.md — lite version
266
+ const liteClaudeSrc = path.join(sourceDir, 'templates', 'lite', 'CLAUDE.md');
267
+ if (fs.existsSync(liteClaudeSrc)) {
268
+ fs.copyFileSync(liteClaudeSrc, path.join(destTemplates, 'CLAUDE.md'));
269
+ }
270
+
271
+ // AGENTS.md — lite version
272
+ const liteAgentsSrc = path.join(sourceDir, 'templates', 'lite', 'AGENTS.md');
273
+ if (fs.existsSync(liteAgentsSrc)) {
274
+ fs.copyFileSync(liteAgentsSrc, path.join(destTemplates, 'AGENTS.md'));
275
+ }
276
+
277
+ // Issue templates — shared between lite and full
278
+ const issueTemplateSrc = path.join(sourceDir, 'templates', '.github', 'ISSUE_TEMPLATE');
279
+ const issueTemplateDest = path.join(destTemplates, '.github', 'ISSUE_TEMPLATE');
280
+ if (fs.existsSync(issueTemplateSrc)) {
281
+ copyDirSync(issueTemplateSrc, issueTemplateDest);
282
+ }
283
+ }
284
+
285
+ function scaffoldFullTemplates(sourceDir, targetDir, projectId, statusFieldId, optionMap, repoOwner, repoName) {
286
+ const destTemplates = path.join(targetDir, '.agentic-pdlc', 'templates');
287
+ fs.mkdirSync(destTemplates, { recursive: true });
288
+
289
+ // CLAUDE.md — concatenate lite + full addon
290
+ const liteClaudeSrc = path.join(sourceDir, 'templates', 'lite', 'CLAUDE.md');
291
+ const fullClaudeSrc = path.join(sourceDir, 'templates', 'full', 'CLAUDE.md');
292
+ if (fs.existsSync(liteClaudeSrc) && fs.existsSync(fullClaudeSrc)) {
293
+ const combined = fs.readFileSync(liteClaudeSrc, 'utf8') + '\n' + fs.readFileSync(fullClaudeSrc, 'utf8');
294
+ fs.writeFileSync(path.join(destTemplates, 'CLAUDE.md'), combined);
295
+ } else if (fs.existsSync(liteClaudeSrc)) {
296
+ fs.copyFileSync(liteClaudeSrc, path.join(destTemplates, 'CLAUDE.md'));
297
+ }
298
+
299
+ // AGENTS.md — full version
300
+ const fullAgentsSrc = path.join(sourceDir, 'templates', 'full', 'AGENTS.md');
301
+ if (fs.existsSync(fullAgentsSrc)) {
302
+ fs.copyFileSync(fullAgentsSrc, path.join(destTemplates, 'AGENTS.md'));
303
+ }
304
+
305
+ // All of templates/.github/ (issue templates + workflows)
306
+ const githubSrc = path.join(sourceDir, 'templates', '.github');
307
+ const githubDest = path.join(destTemplates, '.github');
308
+ if (fs.existsSync(githubSrc)) {
309
+ copyDirSync(githubSrc, githubDest);
310
+ }
311
+
312
+ // docs/pdlc.md — substitute board IDs
313
+ const pdlcSrc = path.join(sourceDir, 'templates', 'full', 'docs', 'pdlc.md');
314
+ const pdlcDest = path.join(destTemplates, 'docs', 'pdlc.md');
315
+ if (fs.existsSync(pdlcSrc)) {
316
+ fs.mkdirSync(path.join(destTemplates, 'docs'), { recursive: true });
317
+ let pdlcContent = fs.readFileSync(pdlcSrc, 'utf8');
318
+ if (projectId) pdlcContent = pdlcContent.replace(/\{\{PROJECT_ID\}\}/g, () => projectId);
319
+ if (statusFieldId) pdlcContent = pdlcContent.replace(/\{\{STATUS_FIELD_ID\}\}/g, () => statusFieldId);
320
+ pdlcContent = pdlcContent.replace(/\{\{REPO_OWNER\}\}/g, () => repoOwner);
321
+ pdlcContent = pdlcContent.replace(/\{\{REPO_NAME\}\}/g, () => repoName);
322
+ if (Object.keys(optionMap).length > 0) {
323
+ pdlcContent = pdlcContent.replace(/\{\{ID_IDEA\}\}/g, () => optionMap['💡 Idea - No move to Exploration directly'] || 'MISSING_ID');
324
+ pdlcContent = pdlcContent.replace(/\{\{ID_EXPLORATION\}\}/g, () => optionMap['🔍 Exploration'] || 'MISSING_ID');
325
+ pdlcContent = pdlcContent.replace(/\{\{ID_BRAINSTORMING\}\}/g, () => optionMap['🧠 Brainstorming'] || 'MISSING_ID');
326
+ pdlcContent = pdlcContent.replace(/\{\{ID_DETAIL\}\}/g, () => optionMap['📐 Detail Solution'] || 'MISSING_ID');
327
+ pdlcContent = pdlcContent.replace(/\{\{ID_APPROVAL\}\}/g, () => optionMap['✅ Approval'] || 'MISSING_ID');
328
+ pdlcContent = pdlcContent.replace(/\{\{ID_DEVELOPMENT\}\}/g, () => optionMap['⚙️ Development'] || 'MISSING_ID');
329
+ pdlcContent = pdlcContent.replace(/\{\{ID_TESTING\}\}/g, () => optionMap['🧪 Testing'] || 'MISSING_ID');
330
+ pdlcContent = pdlcContent.replace(/\{\{ID_CODE_REVIEW_PR\}\}/g, () => optionMap['👁 Code Review / PR'] || 'MISSING_ID');
331
+ pdlcContent = pdlcContent.replace(/\{\{ID_READY_FOR_PRODUCTION\}\}/g,() => optionMap['🚀 Ready for Production']|| 'MISSING_ID');
158
332
  }
159
- if (scopesAfter.length > 0) {
160
- console.log(`\n${green}✅ Token refreshed. Active scopes: ${scopesAfter.join(', ')}${reset}\n`);
333
+ fs.writeFileSync(pdlcDest, pdlcContent);
334
+ if (projectId && statusFieldId && Object.keys(optionMap).length > 0) {
335
+ console.log(`${i18n.pdlc_prefilled}`);
161
336
  } else {
162
- console.log(`\n${green} Token refreshed with 'project' scope.${reset}\n`);
337
+ console.log(`${yellow}⚠️ pdlc.md copied Project IDs not filled (board creation failed). Re-run after fixing token.${reset}`);
163
338
  }
164
339
  }
165
340
 
341
+ // project-automation.yml — substitute IDs
342
+ const paPath = path.join(destTemplates, '.github', 'workflows', 'project-automation.yml');
343
+ if (fs.existsSync(paPath) && Object.keys(optionMap).length > 0) {
344
+ let wfContent = fs.readFileSync(paPath, 'utf8');
345
+ if (projectId) wfContent = wfContent.replace(/\{\{PROJECT_ID\}\}/g, () => projectId);
346
+ if (statusFieldId) wfContent = wfContent.replace(/\{\{STATUS_FIELD_ID\}\}/g, () => statusFieldId);
347
+ wfContent = wfContent.replace(/\{\{ID_IDEA\}\}/g, () => optionMap['💡 Idea - No move to Exploration directly'] || 'MISSING_ID');
348
+ wfContent = wfContent.replace(/\{\{ID_EXPLORATION\}\}/g, () => optionMap['🔍 Exploration'] || 'MISSING_ID');
349
+ wfContent = wfContent.replace(/\{\{ID_BRAINSTORMING\}\}/g, () => optionMap['🧠 Brainstorming'] || 'MISSING_ID');
350
+ wfContent = wfContent.replace(/\{\{ID_DETAILING\}\}/g, () => optionMap['📐 Detail Solution'] || 'MISSING_ID');
351
+ wfContent = wfContent.replace(/\{\{ID_APPROVAL\}\}/g, () => optionMap['✅ Approval'] || 'MISSING_ID');
352
+ wfContent = wfContent.replace(/\{\{ID_DEVELOPMENT\}\}/g, () => optionMap['⚙️ Development'] || 'MISSING_ID');
353
+ wfContent = wfContent.replace(/\{\{ID_TESTING\}\}/g, () => optionMap['🧪 Testing'] || 'MISSING_ID');
354
+ wfContent = wfContent.replace(/\{\{ID_CODE_REVIEW_PR\}\}/g, () => optionMap['👁 Code Review / PR'] || 'MISSING_ID');
355
+ wfContent = wfContent.replace(/\{\{ID_PRODUCTION\}\}/g, () => optionMap['🚀 Ready for Production']|| 'MISSING_ID');
356
+ fs.writeFileSync(paPath, wfContent);
357
+ }
358
+
359
+ console.log(`${i18n.templates_copied}`);
360
+ }
361
+
362
+ function writeCliContext(targetDir, profile, data) {
363
+ try {
364
+ const contextPath = path.join(targetDir, '.agentic-pdlc', 'cli-context.json');
365
+ fs.mkdirSync(path.join(targetDir, '.agentic-pdlc'), { recursive: true });
366
+ fs.writeFileSync(contextPath, JSON.stringify({ profile, ...data }, null, 2));
367
+ } catch (_) {
368
+ // Non-fatal — agent will ask for the values instead
369
+ }
370
+ }
371
+
372
+ // ─── runFullSetup ─────────────────────────────────────────────────────────────
373
+
374
+ async function runFullSetup() {
375
+ await checkGhAuth();
376
+ await checkAndRefreshProjectScope();
377
+
166
378
  const agentAnswer = await askQuestion(i18n.ask_agent);
167
379
  const agent = agentAnswer.trim().toLowerCase();
168
380
  if (!['claude', 'cursor', 'copilot'].includes(agent)) {
@@ -201,21 +413,7 @@ async function runSetup() {
201
413
  console.log(`\n${yellow}${i18n.starting_setup}${reset}`);
202
414
 
203
415
  // Labels
204
- const labels = [
205
- { name: 'stage:exploration', color: '9b59b6', description: 'Issue is being evaluated' },
206
- { name: 'stage:brainstorming', color: 'e84393', description: 'Proposed approaches awaiting PM gate' },
207
- { name: 'stage:detailing', color: '3498db', description: 'Technical spec is being written' },
208
- { name: 'stage:development', color: 'e67e22', description: 'Agent is implementing the spec' },
209
- { name: 'stage:testing', color: '8e44ad', description: 'Agent is testing the implementation' },
210
- { name: 'spec:approved', color: '0e8a16', description: 'Spec approved — agent can implement' },
211
- { name: 'pr:in-review', color: 'e4e669', description: 'PR awaiting code review' },
212
- { name: 'pr:approved', color: '0e8a16', description: 'PR approved, ready for merge' },
213
- { name: 'architecture-violation', color: 'd93f0b', description: 'Invariant violation detected by CI' },
214
- { name: 'qa:approved', color: '0e8a16', description: 'QA Agent approved the implementation' },
215
- { name: 'qa:needs-work', color: 'd93f0b', description: 'QA Agent found issues' },
216
- { name: 'infra:qa-broken', color: 'F97316', description: 'QA Agent failed to run — manual review required' },
217
- { name: 'jules', color: '5319e7', description: 'Jules AI Agent' }
218
- ];
416
+ const labels = PDLC_LABELS;
219
417
 
220
418
  console.log(`\n${cyan}${i18n.creating_labels}${reset}`);
221
419
  for (const label of labels) {
@@ -355,145 +553,26 @@ async function runSetup() {
355
553
  console.log(`\n${yellow}ℹ️ Org repo detected — PROJECT_PAT will require manual setup for security.${reset}`);
356
554
  }
357
555
 
358
- console.log(`\n${yellow}${i18n.scaffolding}${reset}`);
359
-
360
- // We copy the templates folder so the agent has the real text logic to replace and rename
361
- const sourceTemplates = path.join(sourceDir, 'templates');
362
- const targetTemplates = path.join(targetDir, '.agentic-pdlc', 'templates');
363
-
364
- if (fs.existsSync(sourceTemplates)) {
365
- copyDirSync(sourceTemplates, targetTemplates);
366
- console.log(`${i18n.templates_copied}`);
367
-
368
- // Substitute values in docs/pdlc.md automatically
369
- const pdlcDest = path.join(targetTemplates, 'docs', 'pdlc.md');
370
- if (fs.existsSync(pdlcDest)) {
371
- let pdlcContent = fs.readFileSync(pdlcDest, 'utf8');
372
-
373
- if (projectId) pdlcContent = pdlcContent.replace(/\{\{PROJECT_ID\}\}/g, () => projectId);
374
- if (statusFieldId) pdlcContent = pdlcContent.replace(/\{\{STATUS_FIELD_ID\}\}/g, () => statusFieldId);
375
- pdlcContent = pdlcContent.replace(/\{\{REPO_OWNER\}\}/g, () => repoOwner);
376
- pdlcContent = pdlcContent.replace(/\{\{REPO_NAME\}\}/g, () => repoName);
377
-
378
- if (Object.keys(optionMap).length > 0) {
379
- pdlcContent = pdlcContent.replace(/\{\{ID_IDEA\}\}/g, optionMap["💡 Idea"] || 'MISSING_ID');
380
- pdlcContent = pdlcContent.replace(/\{\{ID_EXPLORATION\}\}/g, optionMap["🔍 Exploration"] || 'MISSING_ID');
381
- pdlcContent = pdlcContent.replace(/\{\{ID_BRAINSTORMING\}\}/g, optionMap["🧠 Brainstorming"] || 'MISSING_ID');
382
- pdlcContent = pdlcContent.replace(/\{\{ID_DETAIL\}\}/g, optionMap["📐 Detail Solution"] || 'MISSING_ID');
383
- pdlcContent = pdlcContent.replace(/\{\{ID_APPROVAL\}\}/g, optionMap["✅ Approval"] || 'MISSING_ID');
384
- pdlcContent = pdlcContent.replace(/\{\{ID_DEVELOPMENT\}\}/g, optionMap["⚙️ Development"] || 'MISSING_ID');
385
- pdlcContent = pdlcContent.replace(/\{\{ID_TESTING\}\}/g, optionMap["🧪 Testing"] || 'MISSING_ID');
386
- pdlcContent = pdlcContent.replace(/\{\{ID_CODE_REVIEW_PR\}\}/g, optionMap["👁 Code Review / PR"] || 'MISSING_ID');
387
- pdlcContent = pdlcContent.replace(/\{\{ID_READY_FOR_PRODUCTION\}\}/g, optionMap["🚀 Ready for Production"] || 'MISSING_ID');
388
- }
389
-
390
- fs.writeFileSync(pdlcDest, pdlcContent);
391
- if (projectId && statusFieldId && Object.keys(optionMap).length > 0) {
392
- console.log(`${i18n.pdlc_prefilled}`);
393
- } else {
394
- console.log(`${yellow}⚠️ pdlc.md copied — Project IDs not filled (board creation failed). Re-run after fixing token.${reset}`);
395
- }
396
- }
556
+ await setBranchProtection(repo, ['PDLC Stage Gate', 'QA Gate']);
397
557
 
398
- // Pre-fill project-automation.yml with the same IDs so the agent doesn't need to map them
399
- const workflowAutomationPath = path.join(targetTemplates, '.github', 'workflows', 'project-automation.yml');
400
- if (fs.existsSync(workflowAutomationPath)) {
401
- let wfContent = fs.readFileSync(workflowAutomationPath, 'utf8');
402
- if (projectId) wfContent = wfContent.replace(/\{\{PROJECT_ID\}\}/g, () => projectId);
403
- if (statusFieldId) wfContent = wfContent.replace(/\{\{STATUS_FIELD_ID\}\}/g, () => statusFieldId);
404
- if (Object.keys(optionMap).length > 0) {
405
- wfContent = wfContent.replace(/\{\{ID_IDEA\}\}/g, optionMap["💡 Idea"] || 'MISSING_ID');
406
- wfContent = wfContent.replace(/\{\{ID_EXPLORATION\}\}/g, () => optionMap["🔍 Exploration"] || 'MISSING_ID');
407
- wfContent = wfContent.replace(/\{\{ID_BRAINSTORMING\}\}/g, () => optionMap["🧠 Brainstorming"] || 'MISSING_ID');
408
- wfContent = wfContent.replace(/\{\{ID_DETAILING\}\}/g, () => optionMap["📐 Detail Solution"] || 'MISSING_ID');
409
- wfContent = wfContent.replace(/\{\{ID_APPROVAL\}\}/g, () => optionMap["✅ Approval"] || 'MISSING_ID');
410
- wfContent = wfContent.replace(/\{\{ID_DEVELOPMENT\}\}/g, () => optionMap["⚙️ Development"] || 'MISSING_ID');
411
- wfContent = wfContent.replace(/\{\{ID_TESTING\}\}/g, () => optionMap["🧪 Testing"] || 'MISSING_ID');
412
- wfContent = wfContent.replace(/\{\{ID_CODE_REVIEW_PR\}\}/g, () => optionMap["👁 Code Review / PR"] || 'MISSING_ID');
413
- wfContent = wfContent.replace(/\{\{ID_PRODUCTION\}\}/g, () => optionMap["🚀 Ready for Production"] || 'MISSING_ID');
414
- }
415
- fs.writeFileSync(workflowAutomationPath, wfContent);
416
- }
417
- }
558
+ console.log(`\n${yellow}${i18n.scaffolding}${reset}`);
559
+ scaffoldFullTemplates(sourceDir, targetDir, projectId, statusFieldId, optionMap, repoOwner, repoName);
418
560
 
419
561
  // Write CLI context for the agent to consume in Setup Mode
420
- try {
421
- const cliContextPath = path.join(targetDir, '.agentic-pdlc', 'cli-context.json');
422
- const boardUrl = projectNumber ? buildBoardUrl(repoOwner, projectNumber, isOrg) : null;
423
- fs.writeFileSync(cliContextPath, JSON.stringify({
424
- projectName,
425
- repoOwner,
426
- repoName,
427
- projectNumber,
428
- isOrg,
429
- boardUrl,
430
- patAutoSet
431
- }, null, 2));
432
- } catch (err) {
433
- // Non-fatal — agent will ask for the values instead
434
- }
435
-
436
- // Install PDLC stage gate hook (all agents)
437
- const hookSrc = path.join(sourceDir, 'adapters', 'hooks', 'pdlc-stage-gate.sh');
438
- const hookDir = path.join(targetDir, '.agentic-pdlc', 'hooks');
439
- const hookDest = path.join(hookDir, 'pdlc-stage-gate.sh');
440
- if (fs.existsSync(hookSrc)) {
441
- fs.mkdirSync(hookDir, { recursive: true });
442
- fs.copyFileSync(hookSrc, hookDest);
443
- fs.chmodSync(hookDest, '755');
444
- }
445
- const claudeSettingsDir = path.join(targetDir, '.claude');
446
- const claudeSettingsPath = path.join(claudeSettingsDir, 'settings.json');
447
- if (!fs.existsSync(claudeSettingsPath)) {
448
- fs.mkdirSync(claudeSettingsDir, { recursive: true });
449
- fs.writeFileSync(claudeSettingsPath, JSON.stringify({
450
- hooks: {
451
- PreToolUse: [{
452
- matcher: 'Bash',
453
- hooks: [{ type: 'command', command: 'bash .agentic-pdlc/hooks/pdlc-stage-gate.sh' }]
454
- }]
455
- }
456
- }, null, 2) + '\n');
457
- }
562
+ const boardUrl = projectNumber ? buildBoardUrl(repoOwner, projectNumber, isOrg) : null;
563
+ writeCliContext(targetDir, 'full', {
564
+ projectName,
565
+ repoOwner,
566
+ repoName,
567
+ projectNumber,
568
+ isOrg,
569
+ boardUrl,
570
+ patAutoSet
571
+ });
458
572
 
459
- // Handle the specific setup instructions target
460
- const claudeSetupSrc = path.join(sourceDir, 'adapters', 'claude-code', 'skill.md');
461
- const cursorSetupSrc = path.join(sourceDir, 'adapters', 'cursor', 'rules.md');
462
-
463
- if (agent === 'claude') {
464
- if (fs.existsSync(claudeSetupSrc)) {
465
- const dest = path.join(targetDir, '.agentic-setup.md');
466
- fs.copyFileSync(claudeSetupSrc, dest);
467
- console.log(`${i18n.setup_written}`);
468
- printSetupDone();
469
- } else {
470
- console.error(`${i18n.missing_claude}${claudeSetupSrc}`);
471
- }
472
- } else if (agent === 'cursor') {
473
- if (fs.existsSync(cursorSetupSrc)) {
474
- const dest = path.join(targetDir, '.cursorrules');
475
- fs.copyFileSync(cursorSetupSrc, dest);
476
- console.log(`${i18n.cursor_rules_written}`);
573
+ installHook(sourceDir, targetDir);
477
574
 
478
- if (fs.existsSync(claudeSetupSrc)) {
479
- const setupDest = path.join(targetDir, '.agentic-setup.md');
480
- fs.copyFileSync(claudeSetupSrc, setupDest);
481
- console.log(`${i18n.setup_written}`);
482
- }
483
- printSetupDone();
484
- } else {
485
- console.error(`${i18n.missing_claude}${cursorSetupSrc}`);
486
- }
487
- } else {
488
- if (fs.existsSync(claudeSetupSrc)) {
489
- const dest = path.join(targetDir, '.agentic-setup.md');
490
- fs.copyFileSync(claudeSetupSrc, dest);
491
- console.log(`${i18n.setup_written}`);
492
- printSetupDone();
493
- } else {
494
- console.error(`${i18n.missing_claude}${claudeSetupSrc}`);
495
- }
496
- }
575
+ copyAdapterFiles(agent, sourceDir, targetDir);
497
576
 
498
577
  rl.close();
499
578
  }
@@ -596,6 +675,13 @@ async function runUpdate() {
596
675
  process.exit(1);
597
676
  }
598
677
 
678
+ const ctx = JSON.parse(fs.readFileSync(contextPath, 'utf8'));
679
+ if ((ctx.profile || 'full') === 'lite') {
680
+ console.log(`\n${yellow}⚠️ Lite install detected. Run --upgrade-to-agentic to add the full board machine first.${reset}\n`);
681
+ rl.close();
682
+ return;
683
+ }
684
+
599
685
  const state = detectAgentState(targetDir);
600
686
  const sep = '─'.repeat(55);
601
687
 
@@ -669,6 +755,24 @@ async function runUpdate() {
669
755
  }
670
756
  }
671
757
 
758
+ // Upgrade lite → full agentic profile (extend CLAUDE.md, do not replace)
759
+ const claudeMdPath = path.join(targetDir, 'CLAUDE.md');
760
+ const fullClaudeSrc = path.join(sourceDir, 'templates', 'full', 'CLAUDE.md');
761
+ if (fs.existsSync(claudeMdPath) && fs.existsSync(fullClaudeSrc)) {
762
+ const existing = fs.readFileSync(claudeMdPath, 'utf8');
763
+ if (!existing.includes('<!-- agentic-full -->')) {
764
+ console.log(`\n${cyan}— Agentic Profile Upgrade —${reset}`);
765
+ const upgradeAnswer = (await askQuestion(' Extend CLAUDE.md with the full multi-agent pipeline rulebook? (Y/n): ')).trim().toLowerCase();
766
+ if (!['n', 'no', 'não', 'nao'].includes(upgradeAnswer)) {
767
+ const extension = fs.readFileSync(fullClaudeSrc, 'utf8');
768
+ fs.writeFileSync(claudeMdPath, existing.trimEnd() + '\n\n' + extension + '\n');
769
+ results.push('✅ CLAUDE.md extended with full agentic profile');
770
+ } else {
771
+ results.push('⏭ Agentic profile upgrade — skipped');
772
+ }
773
+ }
774
+ }
775
+
672
776
  console.log(`\n${cyan}${sep}${reset}`);
673
777
  for (const r of results) console.log(` ${r}`);
674
778
  console.log(`${cyan}${sep}${reset}\n`);
@@ -676,11 +780,268 @@ async function runUpdate() {
676
780
  rl.close();
677
781
  }
678
782
 
783
+ // ─── resolveMode ──────────────────────────────────────────────────────────────
784
+
785
+ function resolveMode(args) {
786
+ if (args.includes('--update')) return 'update';
787
+ if (args.includes('--upgrade-to-agentic')) return 'upgrade';
788
+ if (args.includes('--agentic')) return 'full';
789
+ return 'lite';
790
+ }
791
+
792
+ // Export for testing
793
+ if (typeof module !== 'undefined') module.exports = { resolveMode };
794
+
795
+ // ─── runLiteSetup ─────────────────────────────────────────────────────────────
796
+
797
+ async function runLiteSetup() {
798
+ await checkGhAuth();
799
+
800
+ const agentAnswer = await askQuestion(i18n.ask_agent);
801
+ const agent = agentAnswer.trim().toLowerCase();
802
+ if (!['claude', 'cursor', 'copilot'].includes(agent)) {
803
+ console.log(t(
804
+ `ℹ️ Generating Universal Setup for '${agent}' (Compatible with any Markdown-reading agent).`,
805
+ `ℹ️ Gerando Setup Universal para '${agent}' (Compatível com qualquer agente que leia Markdown).`,
806
+ `ℹ️ Generando Setup Universal para '${agent}' (Compatible con cualquier agente que lea Markdown).`
807
+ ));
808
+ }
809
+
810
+ let repoOwner, repoName, repo;
811
+ while (true) {
812
+ let repoUrl = (await askQuestion(i18n.ask_repo)).trim();
813
+ if (repoUrl.endsWith('/')) repoUrl = repoUrl.slice(0, -1);
814
+ if (repoUrl.endsWith('.git')) repoUrl = repoUrl.slice(0, -4);
815
+ const repoParts = repoUrl.split('/');
816
+ if (repoParts.length >= 2) {
817
+ repoOwner = repoParts[repoParts.length - 2];
818
+ repoName = repoParts[repoParts.length - 1];
819
+ repo = `${repoOwner}/${repoName}`;
820
+ break;
821
+ }
822
+ console.log(`${red}${i18n.invalid_repo}${reset}`);
823
+ }
824
+
825
+ let isOrg = false;
826
+ try {
827
+ const ownerType = execFileSync('gh', ['api', `repos/${repo}`, '--jq', '.owner.type'],
828
+ { stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim();
829
+ isOrg = ownerType === 'Organization';
830
+ } catch (_) {}
831
+
832
+ console.log(`\n${yellow}${i18n.starting_setup}${reset}`);
833
+
834
+ installHook(sourceDir, targetDir);
835
+
836
+ console.log(`\n${yellow}${i18n.scaffolding}${reset}`);
837
+ scaffoldLiteTemplates(sourceDir, targetDir);
838
+ console.log(`${i18n.templates_copied}`);
839
+
840
+ await setBranchProtection(repo, ['PDLC Stage Gate']);
841
+
842
+ writeCliContext(targetDir, 'lite', {
843
+ repoOwner,
844
+ repoName,
845
+ projectNumber: null,
846
+ isOrg,
847
+ boardUrl: null,
848
+ patAutoSet: false
849
+ });
850
+
851
+ copyAdapterFiles(agent, sourceDir, targetDir);
852
+
853
+ rl.close();
854
+ }
855
+
856
+ async function runUpgradeToAgentic() {
857
+ const contextPath = path.join(targetDir, '.agentic-pdlc', 'cli-context.json');
858
+ if (!fs.existsSync(contextPath)) {
859
+ console.error(`\n${red}${i18n.update_no_context}${reset}\n`);
860
+ rl.close();
861
+ process.exit(1);
862
+ }
863
+
864
+ const ctx = JSON.parse(fs.readFileSync(contextPath, 'utf8'));
865
+ if ((ctx.profile || 'full') === 'full') {
866
+ console.log(`\n${green}✅ Already running full profile. Nothing to upgrade.${reset}\n`);
867
+ rl.close();
868
+ return;
869
+ }
870
+
871
+ await checkGhAuth();
872
+ await checkAndRefreshProjectScope();
873
+
874
+ const { repoOwner, repoName } = ctx;
875
+ const repo = `${repoOwner}/${repoName}`;
876
+
877
+ const askProjectName = t(
878
+ `What is the project name for the board? (default: ${repoName.toUpperCase()}): `,
879
+ `Qual o nome do projeto em que o board será configurado? (padrão: ${repoName.toUpperCase()}): `,
880
+ `¿Cuál es el nombre del proyecto en el que se configurará el board? (por defecto: ${repoName.toUpperCase()}): `
881
+ );
882
+ const projectNameAnswer = await askQuestion(askProjectName);
883
+ const projectName = projectNameAnswer.trim() ? projectNameAnswer.trim().toUpperCase() : repoName.toUpperCase();
884
+ const boardName = `BOARD - ${projectName}`;
885
+
886
+ let isOrg = ctx.isOrg || false;
887
+ try {
888
+ const ownerType = execFileSync('gh', ['api', `repos/${repo}`, '--jq', '.owner.type'],
889
+ { stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim();
890
+ isOrg = ownerType === 'Organization';
891
+ } catch (_) {}
892
+
893
+ console.log(`\n${yellow}${i18n.starting_setup}${reset}`);
894
+
895
+ // Labels
896
+ const labels = PDLC_LABELS;
897
+
898
+ console.log(`\n${cyan}${i18n.creating_labels}${reset}`);
899
+ for (const label of labels) {
900
+ try {
901
+ execFileSync('gh', ['label', 'create', label.name, '--color', label.color, '--description', label.description, '--repo', repo, '--force'], { stdio: 'ignore' });
902
+ console.log(` ${i18n.label_ok}${label.name}`);
903
+ } catch (err) {
904
+ console.log(` ${i18n.label_warn}${label.name}`);
905
+ }
906
+ }
907
+
908
+ // Board
909
+ console.log(`\n${cyan}${i18n.creating_project}${reset}`);
910
+ let ownerId, projectId, projectNumber;
911
+ try {
912
+ if (isOrg) {
913
+ ownerId = execFileSync('gh', ['api', 'graphql', '-f', 'query=query($login: String!) { organization(login: $login) { id } }', '-f', `login=${repoOwner}`, '--jq', '.data.organization.id'],
914
+ { stdio: ['ignore', 'pipe', 'pipe'] }).toString().trim();
915
+ } else {
916
+ ownerId = execFileSync('gh', ['api', 'graphql', '-f', 'query={ viewer { id } }', '--jq', '.data.viewer.id'],
917
+ { stdio: ['ignore', 'pipe', 'pipe'] }).toString().trim();
918
+ }
919
+
920
+ const raw = execFileSync('gh', ['api', 'graphql', '-f',
921
+ 'query=mutation($owner: ID!, $title: String!) { createProjectV2(input: {ownerId: $owner, title: $title}) { projectV2 { id number } } }',
922
+ '-f', `owner=${ownerId}`, '-f', `title=${boardName}`],
923
+ { stdio: ['ignore', 'pipe', 'pipe'] }).toString().trim();
924
+ const resp = raw ? JSON.parse(raw) : null;
925
+ if (resp?.errors) throw new Error(resp.errors.map(e => e.message).join('; '));
926
+ const pData = resp?.data?.createProjectV2?.projectV2;
927
+ projectId = pData?.id;
928
+ projectNumber = pData?.number;
929
+ console.log(` ${i18n.project_ok}${projectId})`);
930
+
931
+ try {
932
+ const repoNodeId = execFileSync('gh', ['api', `repos/${repo}`, '--jq', '.node_id'],
933
+ { stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim();
934
+ execFileSync('gh', ['api', 'graphql', '-f',
935
+ 'query=mutation($projectId: ID!, $repositoryId: ID!) { linkProjectV2ToRepository(input: {projectId: $projectId, repositoryId: $repositoryId}) { repository { name } } }',
936
+ '-f', `projectId=${projectId}`, '-f', `repositoryId=${repoNodeId}`],
937
+ { stdio: 'ignore' });
938
+ console.log(` ${i18n.link_project_ok}`);
939
+ } catch (_) {
940
+ console.log(` ${i18n.link_project_warn}`);
941
+ }
942
+ } catch (err) {
943
+ console.log(` ${i18n.project_err}${err.message}`);
944
+ }
945
+
946
+ let statusFieldId;
947
+ let optionMap = {};
948
+
949
+ if (projectId) {
950
+ console.log(` ${cyan}${i18n.config_columns}${reset}`);
951
+ try {
952
+ statusFieldId = execFileSync('gh', ['api', 'graphql', '-f',
953
+ 'query=query($projectId: ID!) { node(id: $projectId) { ... on ProjectV2 { fields(first: 20) { nodes { ... on ProjectV2SingleSelectField { id name } } } } } }',
954
+ '-f', `projectId=${projectId}`, '--jq', '.data.node.fields.nodes[] | select(.name == "Status") | .id'
955
+ ]).toString().trim();
956
+
957
+ if (statusFieldId) {
958
+ const columns = [
959
+ { name: '💡 Idea - No move to Exploration directly', description: 'Just tell your agent to work on issue #XX', color: 'GRAY' },
960
+ { name: '🔍 Exploration', description: 'AI is analyzing code and context', color: 'PURPLE' },
961
+ { name: '🧠 Brainstorming', description: 'AI proposed approaches and trade-offs', color: 'PINK' },
962
+ { name: '📐 Detail Solution', description: 'AI is writing the technical spec', color: 'BLUE' },
963
+ { name: '✅ Approval', description: 'Spec ready, awaiting `spec:approved` label', color: 'GREEN' },
964
+ { name: '⚙️ Development', description: 'AI implementing the spec', color: 'ORANGE' },
965
+ { name: '🧪 Testing', description: 'QA testing and CI pipeline checks', color: 'RED' },
966
+ { name: '👁 Code Review / PR',description: 'PR opened, awaiting your review', color: 'YELLOW' },
967
+ { name: '🚀 Ready for Production', description: 'Merged and ready for production', color: 'GREEN' }
968
+ ];
969
+
970
+ const queryPayload = JSON.stringify({
971
+ query: `mutation($fieldId: ID!, $options: [ProjectV2SingleSelectFieldOptionInput!]) {
972
+ updateProjectV2Field(input: { fieldId: $fieldId, singleSelectOptions: $options }) {
973
+ projectV2Field { ... on ProjectV2SingleSelectField { options { id name } } }
974
+ }
975
+ }`,
976
+ variables: { fieldId: statusFieldId, options: columns }
977
+ });
978
+
979
+ const updateOutput = execFileSync('gh', ['api', 'graphql', '--input', '-'],
980
+ { input: queryPayload }).toString().trim();
981
+ const jsonResponse = updateOutput ? JSON.parse(updateOutput) : null;
982
+ const returnedOptions = jsonResponse?.data?.updateProjectV2Field?.projectV2Field?.options || [];
983
+ for (const opt of returnedOptions) optionMap[opt.name] = opt.id;
984
+ console.log(` ${i18n.columns_ok}`);
985
+ }
986
+ } catch (_) {
987
+ console.log(` ${i18n.columns_warn}`);
988
+ }
989
+ }
990
+
991
+ // Auto-provision PROJECT_PAT for personal repos
992
+ let patAutoSet = false;
993
+ if (projectId && !isOrg) {
994
+ try {
995
+ const tokenOut = execFileSync('gh', ['auth', 'token'],
996
+ { stdio: ['ignore', 'pipe', 'pipe'], encoding: 'utf8' }).trim();
997
+ if (tokenOut) {
998
+ execFileSync('gh', ['secret', 'set', 'PROJECT_PAT', '--body', tokenOut, '--repo', repo],
999
+ { stdio: ['ignore', 'pipe', 'pipe'] });
1000
+ patAutoSet = true;
1001
+ console.log(`\n${green}✅ PROJECT_PAT secret set automatically (uses your gh OAuth token).${reset}`);
1002
+ }
1003
+ } catch (_) {
1004
+ console.log(`\n${yellow}⚠️ Could not auto-set PROJECT_PAT. Agent will guide manual setup.${reset}`);
1005
+ }
1006
+ }
1007
+
1008
+ console.log(`\n${yellow}${i18n.scaffolding}${reset}`);
1009
+ scaffoldFullTemplates(sourceDir, targetDir, projectId, statusFieldId, optionMap, repoOwner, repoName);
1010
+
1011
+ await setBranchProtection(repo, ['PDLC Stage Gate', 'QA Gate']);
1012
+
1013
+ const boardUrl = projectNumber ? buildBoardUrl(repoOwner, projectNumber, isOrg) : null;
1014
+ writeCliContext(targetDir, 'full', {
1015
+ projectName,
1016
+ repoOwner,
1017
+ repoName,
1018
+ projectNumber,
1019
+ isOrg,
1020
+ boardUrl,
1021
+ patAutoSet
1022
+ });
1023
+
1024
+ const line1 = t('🎉 Upgrade complete! Board:', '🎉 Upgrade concluído! Board:', '🎉 ¡Actualización completada! Board:');
1025
+ console.log(`\n${green}${line1} ${boardUrl || '(board creation failed)'}${reset}\n`);
1026
+
1027
+ rl.close();
1028
+ }
1029
+
679
1030
  // ─── Entry point ──────────────────────────────────────────────────────────────
680
1031
 
681
- const args = process.argv.slice(2);
682
- if (args.includes('--update')) {
683
- runUpdate().catch(err => { console.error(err.message); rl.close(); process.exit(1); });
684
- } else {
685
- runSetup().catch(err => { console.error(err.message); rl.close(); process.exit(1); });
1032
+ if (require.main === module) {
1033
+ console.log(`${cyan}================================================================${reset}`);
1034
+ console.log(`${cyan}${i18n.welcome}${reset}`);
1035
+ console.log(`${cyan}================================================================${reset}\n`);
1036
+
1037
+ const args = process.argv.slice(2);
1038
+ const mode = resolveMode(args);
1039
+
1040
+ const handler =
1041
+ mode === 'update' ? runUpdate :
1042
+ mode === 'upgrade' ? runUpgradeToAgentic :
1043
+ mode === 'full' ? runFullSetup :
1044
+ runLiteSetup;
1045
+
1046
+ handler().catch(err => { console.error(err.message); rl.close(); process.exit(1); });
686
1047
  }