create-agentic-pdlc 2.1.2 → 2.1.4
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.
|
@@ -3,6 +3,8 @@ name: agentic-pdlc
|
|
|
3
3
|
description: Orchestrates the Agentic Product Development Life Cycle (PDLC) upstream stages (Idea -> Spec) and includes an interactive Setup Mode to initialize the framework.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
+
> **ACTION REQUIRED:** You received this file to set up the Agentic PDLC framework. Execute **Setup Mode** now — follow the instructions below step by step. Do not summarize; execute.
|
|
7
|
+
|
|
6
8
|
# Agentic PDLC Orchestrator
|
|
7
9
|
|
|
8
10
|
You are the upstream brainstorm partner and orchestrator for the Agentic PDLC framework. Your role is primarily to define technical specs based on user ideas.
|
|
@@ -40,7 +42,7 @@ If any of these files are missing, you are in **Setup Mode**. Do not proceed wit
|
|
|
40
42
|
- c) **Yes, activate** — *Uncomment the `move-violation-to-board` job in `project-automation.yml`.* → Activate immediately.
|
|
41
43
|
- **QA Agent:** Ask: *"Do you want to use a QA agent to verify PRs automatically before Code Review?"* Present the options:
|
|
42
44
|
- a) **No (Variant A)** — *PRs go straight to Code Review. Standard and simpler.*
|
|
43
|
-
- b) **Yes (Variant B), but I need help configuring it** — *PRs pass through a QA Agent before being reviewed. Requires a
|
|
45
|
+
- b) **Yes (Variant B), but I need help configuring it** — *PRs pass through a QA Agent before being reviewed. Requires a `GEMINI_API_KEY` secret (free tier available at ai.google.dev).* → Guide the user through configuration.
|
|
44
46
|
- c) **Yes (Variant B), I already have it configured** — *PRs pass through a QA Agent before being reviewed.* → Activate Variant B immediately: change `STATUS_CODE_REVIEW_PR` to `STATUS_TESTING` in the `move-card-on-pr-open` job and uncomment the `move-card-on-qa-pass` job in `project-automation.yml`.
|
|
45
47
|
- **Implementation Agent:** Ask: *"Do you use an autonomous implementation agent? (It implements the features you approve for development)"* Present the options:
|
|
46
48
|
- a) **No** — *No autonomous implementation agent.*
|
package/bin/cli.js
CHANGED
|
@@ -40,14 +40,10 @@ const i18n = {
|
|
|
40
40
|
ask_repo: t('What is your GitHub repository URL? (e.g., https://github.com/YOUR_USER/repo_name): ', 'Qual é a URL do seu repositório no GitHub? (ex: https://github.com/SEU_USUARIO/repo_name): ', '¿Cuál es la URL de tu repositorio en GitHub? (ej: https://github.com/TU_USUARIO/repo_name): '),
|
|
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
|
-
ask_branch: t('What is your main branch name? (default: main): ', 'Qual o nome da sua branch principal? (padrão: main): ', '¿Cuál es el nombre de tu rama principal? (por defecto: main): '),
|
|
44
43
|
starting_setup: t('Starting automated repository setup...', 'Iniciando o setup automatizado do repositório...', 'Iniciando la configuración automatizada del repositorio...'),
|
|
45
44
|
creating_labels: t('[1/2] Creating repository labels...', '[1/2] Criando labels no repositório...', '[1/2] Creando etiquetas (labels) en el repositorio...'),
|
|
46
45
|
label_ok: t('✅ Label created: ', '✅ Label criada: ', '✅ Etiqueta creada: '),
|
|
47
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): '),
|
|
48
|
-
applying_protection: t('[2/3] Applying branch protection on ', '[2/3] Aplicando proteção de branch em ', '[2/3] Aplicando protección de rama en '),
|
|
49
|
-
protection_ok: t('✅ Branch protection applied to ', '✅ Proteção de branch aplicada em ', '✅ Protección de rama aplicada a '),
|
|
50
|
-
protection_warn: t('⚠️ Failed to apply branch protection. (Do you have GitHub Pro or is the repo Public?)', '⚠️ Falha ao aplicar proteção de branch. (Você tem GitHub Pro ou o repo é Público?)', '⚠️ Fallo al aplicar protección de rama. (¿Tienes GitHub Pro o el repositorio es Público?)'),
|
|
51
47
|
creating_project: t('[2/2] Creating Project V2 Board...', '[2/2] Criando Project V2 Board...', '[2/2] Creando Project V2 Board...'),
|
|
52
48
|
project_ok: t('✅ Project created (ID: ', '✅ Projeto criado (ID: ', '✅ Proyecto creado (ID: '),
|
|
53
49
|
project_err: t('❌ Failed to create project. Error: ', '❌ Falha ao criar o projeto. Erro: ', '❌ Fallo al crear el proyecto. Error: '),
|
|
@@ -60,19 +56,8 @@ const i18n = {
|
|
|
60
56
|
templates_copied: t('✅ Templates copied to .agentic-pdlc/templates/', '✅ Templates copiados para .agentic-pdlc/templates/', '✅ Plantillas copiadas a .agentic-pdlc/templates/'),
|
|
61
57
|
pdlc_prefilled: t('✅ Pre-filled pdlc.md with Project ID, Status Field ID, and Column Option IDs.', '✅ pdlc.md preenchido com Project ID, Status Field ID, e Column Option IDs.', '✅ pdlc.md completado con Project ID, Status Field ID y Column Option IDs.'),
|
|
62
58
|
setup_written: t('✅ Setup agent profile written to .agentic-setup.md\n', '✅ Perfil de setup do agente salvo em .agentic-setup.md\n', '✅ Perfil de configuración del agente guardado en .agentic-setup.md\n'),
|
|
63
|
-
framework_scaffolded: t('🎉 Framework files scaffolded to .agentic-pdlc/templates/', '🎉 Arquivos do framework injetados em .agentic-pdlc/templates/', '🎉 Archivos del framework inyectados en .agentic-pdlc/templates/'),
|
|
64
|
-
next_steps: t('👉 NEXT STEPS:', '👉 PRÓXIMOS PASSOS:', '👉 PRÓXIMOS PASOS:'),
|
|
65
|
-
step_1: t('1. Open your AI Assistant (Claude, Cursor, Copilot, etc).', '1. Abra o seu Assistente de IA (Claude, Cursor, Copilot, etc).', '1. Abre tu Asistente de IA (Claude, Cursor, Copilot, etc).'),
|
|
66
|
-
step_2: t('2. Ask it to read the .agentic-setup.md and start Setup Mode in any language you prefer. Example 👇', '2. Peça para ele ler o .agentic-setup.md e iniciar o Setup Mode. Exemplo 👇', '2. Pídele que lea .agentic-setup.md e inicie el Setup Mode. Ejemplo 👇'),
|
|
67
|
-
note_cleanup: t('Note: The agent will clean up the .agentic-setup.md file automatically when finished.\n', 'Nota: O agente irá limpar o arquivo .agentic-setup.md automaticamente quando terminar.\n', 'Nota: El agente limpiará el archivo .agentic-setup.md automáticamente cuando termine.\n'),
|
|
68
59
|
missing_claude: t('❌ Could not find instruction file at ', '❌ Não foi possível encontrar o arquivo de instrução em ', '❌ No se pudo encontrar el archivo de instrucción en '),
|
|
69
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'),
|
|
70
|
-
cursor_setup_written: t('✅ Framework Setup Instructions written to .agentic-pdlc/SETUP_PROMPT.md', '✅ Instruções de Setup do Framework salvas em .agentic-pdlc/SETUP_PROMPT.md', '✅ Instrucciones de Setup del Framework guardadas en .agentic-pdlc/SETUP_PROMPT.md'),
|
|
71
|
-
cursor_done: t('🎉 Done! To start the conversational setup:', '🎉 Pronto! Para iniciar o setup conversacional:', '🎉 ¡Listo! Para iniciar la configuración conversacional:'),
|
|
72
|
-
cursor_step_1: t('\t1. Open Cursor', '\t1. Abra o Cursor', '\t1. Abre Cursor'),
|
|
73
|
-
cursor_step_2: t('\t2. Open Composer (Cmd+I or Cmd+L) and type: "@.agentic-pdlc/SETUP_PROMPT.md execute Setup Mode"\n', '\t2. Abra o Composer (Cmd+I ou Cmd+L) e digite: "@.agentic-pdlc/SETUP_PROMPT.md execute Setup Mode"\n', '\t2. Abre Composer (Cmd+I o Cmd+L) y escribe: "@.agentic-pdlc/SETUP_PROMPT.md execute Setup Mode"\n'),
|
|
74
|
-
generic_written: t('✅ Agent generic setup instructions written to .agentic-setup.md', '✅ Instruções genéricas salvas em .agentic-setup.md', '✅ Instrucciones genéricas guardadas en .agentic-setup.md'),
|
|
75
|
-
generic_done: t('Tell your AI agent to read and execute the .agentic-setup.md file!\n', 'Diga ao seu agente para ler e executar o arquivo .agentic-setup.md!\n', '¡Dile a tu agente de IA que lea y ejecute el archivo .agentic-setup.md!\n'),
|
|
76
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:'),
|
|
77
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!')
|
|
78
63
|
};
|
|
@@ -159,14 +144,6 @@ async function runSetup() {
|
|
|
159
144
|
isOrg = accountTypeAnswer.trim().toLowerCase() === 'org' || accountTypeAnswer.trim().toLowerCase() === 'organization';
|
|
160
145
|
}
|
|
161
146
|
|
|
162
|
-
let branchName = 'main';
|
|
163
|
-
try {
|
|
164
|
-
branchName = execFileSync('gh', ['api', `repos/${repo}`, '--jq', '.default_branch'], { stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim() || 'main';
|
|
165
|
-
} catch (err) {
|
|
166
|
-
const branchAnswer = await askQuestion(i18n.ask_branch);
|
|
167
|
-
if (branchAnswer.trim()) branchName = branchAnswer.trim();
|
|
168
|
-
}
|
|
169
|
-
|
|
170
147
|
console.log(`\n${yellow}${i18n.starting_setup}${reset}`);
|
|
171
148
|
|
|
172
149
|
// Labels
|
|
@@ -182,6 +159,7 @@ async function runSetup() {
|
|
|
182
159
|
{ name: 'architecture-violation', color: 'd93f0b', description: 'Invariant violation detected by CI' },
|
|
183
160
|
{ name: 'qa:approved', color: '0e8a16', description: 'QA Agent approved the implementation' },
|
|
184
161
|
{ name: 'qa:needs-work', color: 'd93f0b', description: 'QA Agent found issues' },
|
|
162
|
+
{ name: 'infra:qa-broken', color: 'F97316', description: 'QA Agent failed to run — manual review required' },
|
|
185
163
|
{ name: 'jules', color: '5319e7', description: 'Jules AI Agent' }
|
|
186
164
|
];
|
|
187
165
|
|
|
@@ -208,7 +186,11 @@ async function runSetup() {
|
|
|
208
186
|
}
|
|
209
187
|
|
|
210
188
|
const projectCreateRaw = execFileSync('gh', ['api', 'graphql', '-f', 'query=mutation($owner: ID!, $title: String!) { createProjectV2(input: {ownerId: $owner, title: $title}) { projectV2 { id number } } }', '-f', `owner=${ownerId}`, '-f', `title=${boardName}`]).toString().trim();
|
|
211
|
-
const
|
|
189
|
+
const projectCreateResponse = JSON.parse(projectCreateRaw);
|
|
190
|
+
if (projectCreateResponse.errors) {
|
|
191
|
+
throw new Error(projectCreateResponse.errors.map(e => e.message).join('; '));
|
|
192
|
+
}
|
|
193
|
+
const projectCreateData = projectCreateResponse.data.createProjectV2.projectV2;
|
|
212
194
|
projectId = projectCreateData.id;
|
|
213
195
|
projectNumber = projectCreateData.number;
|
|
214
196
|
|
|
@@ -328,14 +310,14 @@ async function runSetup() {
|
|
|
328
310
|
if (statusFieldId) wfContent = wfContent.replace(/\{\{STATUS_FIELD_ID\}\}/g, () => statusFieldId);
|
|
329
311
|
if (Object.keys(optionMap).length > 0) {
|
|
330
312
|
wfContent = wfContent.replace(/\{\{ID_IDEA\}\}/g, optionMap["💡 Idea"] || 'MISSING_ID');
|
|
331
|
-
wfContent = wfContent.replace(/\{\{ID_EXPLORATION\}\}/g, optionMap["🔍 Exploration"] || 'MISSING_ID');
|
|
332
|
-
wfContent = wfContent.replace(/\{\{ID_BRAINSTORMING\}\}/g, optionMap["🧠 Brainstorming"] || 'MISSING_ID');
|
|
333
|
-
wfContent = wfContent.replace(/\{\{ID_DETAILING\}\}/g, optionMap["📐 Detail Solution"] || 'MISSING_ID');
|
|
334
|
-
wfContent = wfContent.replace(/\{\{ID_APPROVAL\}\}/g, optionMap["✅ Approval"] || 'MISSING_ID');
|
|
335
|
-
wfContent = wfContent.replace(/\{\{ID_DEVELOPMENT\}\}/g, optionMap["⚙️ Development"] || 'MISSING_ID');
|
|
336
|
-
wfContent = wfContent.replace(/\{\{ID_TESTING\}\}/g, optionMap["🧪 Testing"] || 'MISSING_ID');
|
|
337
|
-
wfContent = wfContent.replace(/\{\{ID_CODE_REVIEW_PR\}\}/g, optionMap["👁 Code Review / PR"] || 'MISSING_ID');
|
|
338
|
-
wfContent = wfContent.replace(/\{\{ID_PRODUCTION\}\}/g, optionMap["🚀 Ready for Production"] || 'MISSING_ID');
|
|
313
|
+
wfContent = wfContent.replace(/\{\{ID_EXPLORATION\}\}/g, () => optionMap["🔍 Exploration"] || 'MISSING_ID');
|
|
314
|
+
wfContent = wfContent.replace(/\{\{ID_BRAINSTORMING\}\}/g, () => optionMap["🧠 Brainstorming"] || 'MISSING_ID');
|
|
315
|
+
wfContent = wfContent.replace(/\{\{ID_DETAILING\}\}/g, () => optionMap["📐 Detail Solution"] || 'MISSING_ID');
|
|
316
|
+
wfContent = wfContent.replace(/\{\{ID_APPROVAL\}\}/g, () => optionMap["✅ Approval"] || 'MISSING_ID');
|
|
317
|
+
wfContent = wfContent.replace(/\{\{ID_DEVELOPMENT\}\}/g, () => optionMap["⚙️ Development"] || 'MISSING_ID');
|
|
318
|
+
wfContent = wfContent.replace(/\{\{ID_TESTING\}\}/g, () => optionMap["🧪 Testing"] || 'MISSING_ID');
|
|
319
|
+
wfContent = wfContent.replace(/\{\{ID_CODE_REVIEW_PR\}\}/g, () => optionMap["👁 Code Review / PR"] || 'MISSING_ID');
|
|
320
|
+
wfContent = wfContent.replace(/\{\{ID_PRODUCTION\}\}/g, () => optionMap["🚀 Ready for Production"] || 'MISSING_ID');
|
|
339
321
|
}
|
|
340
322
|
fs.writeFileSync(workflowAutomationPath, wfContent);
|
|
341
323
|
}
|
|
@@ -364,26 +346,28 @@ async function runSetup() {
|
|
|
364
346
|
}
|
|
365
347
|
} else if (agent === 'cursor') {
|
|
366
348
|
if (fs.existsSync(cursorSetupSrc)) {
|
|
367
|
-
// Create .cursorrules which has the general invariants
|
|
368
349
|
const dest = path.join(targetDir, '.cursorrules');
|
|
369
350
|
fs.copyFileSync(cursorSetupSrc, dest);
|
|
370
|
-
|
|
371
|
-
// Also copy skill.md as a setup prompt for cursor composer
|
|
372
|
-
const setupPromptDest = path.join(targetDir, '.agentic-pdlc', 'SETUP_PROMPT.md');
|
|
373
|
-
if (fs.existsSync(claudeSetupSrc)) fs.copyFileSync(claudeSetupSrc, setupPromptDest);
|
|
374
|
-
|
|
375
351
|
console.log(`${i18n.cursor_rules_written}`);
|
|
376
|
-
|
|
352
|
+
|
|
353
|
+
if (fs.existsSync(claudeSetupSrc)) {
|
|
354
|
+
const setupDest = path.join(targetDir, '.agentic-setup.md');
|
|
355
|
+
fs.copyFileSync(claudeSetupSrc, setupDest);
|
|
356
|
+
console.log(`${i18n.setup_written}`);
|
|
357
|
+
}
|
|
377
358
|
printSetupDone();
|
|
378
359
|
} else {
|
|
379
360
|
console.error(`${i18n.missing_claude}${cursorSetupSrc}`);
|
|
380
361
|
}
|
|
381
362
|
} else {
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
363
|
+
if (fs.existsSync(claudeSetupSrc)) {
|
|
364
|
+
const dest = path.join(targetDir, '.agentic-setup.md');
|
|
365
|
+
fs.copyFileSync(claudeSetupSrc, dest);
|
|
366
|
+
console.log(`${i18n.setup_written}`);
|
|
367
|
+
printSetupDone();
|
|
368
|
+
} else {
|
|
369
|
+
console.error(`${i18n.missing_claude}${claudeSetupSrc}`);
|
|
370
|
+
}
|
|
387
371
|
}
|
|
388
372
|
|
|
389
373
|
rl.close();
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@ name: PDLC Board Automation
|
|
|
2
2
|
|
|
3
3
|
on:
|
|
4
4
|
pull_request:
|
|
5
|
-
types: [opened, reopened, closed]
|
|
5
|
+
types: [opened, reopened, closed, labeled]
|
|
6
6
|
pull_request_review:
|
|
7
7
|
types: [submitted]
|
|
8
8
|
issues:
|
|
@@ -189,6 +189,52 @@ jobs:
|
|
|
189
189
|
try { await github.rest.issues.removeLabel({ owner, repo, issue_number: prNumber, name: 'pr:in-review' }); } catch {}
|
|
190
190
|
await github.rest.issues.addLabels({ owner, repo, issue_number: prNumber, labels: ['pr:approved'] }).catch(() => {});
|
|
191
191
|
|
|
192
|
+
# OPTIONAL: Uncomment for Variant B (QA Agent enabled)
|
|
193
|
+
# When qa:approved is added to a PR, move linked issue to Code Review / PR
|
|
194
|
+
# move-card-on-qa-pass:
|
|
195
|
+
# name: qa:approved → Code Review / PR
|
|
196
|
+
# if: github.event_name == 'pull_request' && github.event.action == 'labeled' && github.event.label.name == 'qa:approved'
|
|
197
|
+
# runs-on: ubuntu-latest
|
|
198
|
+
# env:
|
|
199
|
+
# PROJECT_PAT: ${{ secrets.PROJECT_PAT }}
|
|
200
|
+
# steps:
|
|
201
|
+
# - name: Move linked issue to Code Review / PR
|
|
202
|
+
# if: ${{ env.PROJECT_PAT != '' && env.PROJECT_ID != '{{PROJECT_ID}}' }}
|
|
203
|
+
# uses: actions/github-script@v7
|
|
204
|
+
# with:
|
|
205
|
+
# github-token: ${{ env.PROJECT_PAT }}
|
|
206
|
+
# script: |
|
|
207
|
+
# const prNumber = context.payload.pull_request.number;
|
|
208
|
+
# const { owner, repo } = context.repo;
|
|
209
|
+
# const { data: pr } = await github.rest.pulls.get({ owner, repo, pull_number: prNumber });
|
|
210
|
+
# const body = pr.body ?? '';
|
|
211
|
+
# const linkedIssues = [...body.matchAll(/(?:Closes?|Fixes?|Resolves?)\s+#(\d+)/gi)].map(m => parseInt(m[1]));
|
|
212
|
+
# const moveItem = async (nodeId) => {
|
|
213
|
+
# const { addProjectV2ItemById: { item } } = await github.graphql(`
|
|
214
|
+
# mutation($p: ID!, $c: ID!) {
|
|
215
|
+
# addProjectV2ItemById(input: {projectId: $p, contentId: $c}) { item { id } }
|
|
216
|
+
# }`, { p: process.env.PROJECT_ID, c: nodeId });
|
|
217
|
+
# await github.graphql(`
|
|
218
|
+
# mutation($p: ID!, $i: ID!, $f: ID!, $v: ProjectV2FieldValue!) {
|
|
219
|
+
# updateProjectV2ItemFieldValue(input: {projectId: $p, itemId: $i, fieldId: $f, value: $v}) {
|
|
220
|
+
# projectV2Item { id }
|
|
221
|
+
# }
|
|
222
|
+
# }`, {
|
|
223
|
+
# p: process.env.PROJECT_ID, i: item.id, f: process.env.STATUS_FIELD_ID,
|
|
224
|
+
# v: { singleSelectOptionId: process.env.STATUS_CODE_REVIEW_PR }
|
|
225
|
+
# });
|
|
226
|
+
# };
|
|
227
|
+
# if (linkedIssues.length > 0) {
|
|
228
|
+
# for (const n of linkedIssues) {
|
|
229
|
+
# const { data: issue } = await github.rest.issues.get({ owner, repo, issue_number: n });
|
|
230
|
+
# await moveItem(issue.node_id);
|
|
231
|
+
# console.log(`Issue #${n} → Code Review / PR`);
|
|
232
|
+
# }
|
|
233
|
+
# } else {
|
|
234
|
+
# await moveItem(pr.node_id);
|
|
235
|
+
# console.log(`PR #${prNumber} → Code Review / PR (no linked issue)`);
|
|
236
|
+
# }
|
|
237
|
+
|
|
192
238
|
# PR Merged → Production
|
|
193
239
|
move-card-on-pr-merge:
|
|
194
240
|
name: Merged PR → Production
|
|
@@ -6,22 +6,73 @@ on:
|
|
|
6
6
|
permissions:
|
|
7
7
|
pull-requests: write
|
|
8
8
|
contents: read
|
|
9
|
+
issues: read
|
|
9
10
|
|
|
10
11
|
jobs:
|
|
11
12
|
qa:
|
|
12
|
-
name:
|
|
13
|
+
name: AC Coverage Verification (Gemini)
|
|
13
14
|
runs-on: ubuntu-latest
|
|
14
15
|
steps:
|
|
15
16
|
- uses: actions/checkout@v4
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
echo "If tests pass: gh pr edit $PR_URL --add-label 'qa:pass'"
|
|
21
|
-
echo "If tests fail: gh pr edit $PR_URL --add-label 'qa:fail'"
|
|
22
|
-
|
|
23
|
-
# Example success:
|
|
24
|
-
# gh pr edit ${{ github.event.pull_request.html_url }} --add-label "qa:pass"
|
|
17
|
+
with:
|
|
18
|
+
fetch-depth: 0
|
|
19
|
+
|
|
20
|
+
- name: Verify AC Coverage via Gemini
|
|
25
21
|
env:
|
|
26
22
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
27
|
-
|
|
23
|
+
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
|
|
24
|
+
run: |
|
|
25
|
+
set -e
|
|
26
|
+
|
|
27
|
+
PR_NUMBER="${{ github.event.pull_request.number }}"
|
|
28
|
+
BASE="${{ github.event.pull_request.base.sha }}"
|
|
29
|
+
HEAD="${{ github.event.pull_request.head.sha }}"
|
|
30
|
+
|
|
31
|
+
# Get PR diff (truncated to 8000 chars to stay within context limits)
|
|
32
|
+
DIFF=$(git diff "$BASE" "$HEAD" | head -c 8000)
|
|
33
|
+
|
|
34
|
+
# Extract linked issues from PR body
|
|
35
|
+
PR_BODY=$(gh pr view "$PR_NUMBER" --json body --jq '.body // ""')
|
|
36
|
+
ISSUE_NUMS=$(echo "$PR_BODY" | grep -oiE '(Closes?|Fixes?|Resolves?)\s+#([0-9]+)' | grep -oE '[0-9]+' || true)
|
|
37
|
+
|
|
38
|
+
# Build acceptance criteria context
|
|
39
|
+
AC_CONTEXT=""
|
|
40
|
+
if [ -n "$ISSUE_NUMS" ]; then
|
|
41
|
+
for NUM in $ISSUE_NUMS; do
|
|
42
|
+
ISSUE_BODY=$(gh issue view "$NUM" --json body --jq '.body // ""' 2>/dev/null || echo "")
|
|
43
|
+
AC_CONTEXT="${AC_CONTEXT}\\n\\n--- Issue #${NUM} ---\\n${ISSUE_BODY}"
|
|
44
|
+
done
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
if [ -z "$AC_CONTEXT" ]; then
|
|
48
|
+
AC_CONTEXT="No linked issue found. Evaluate if the PR description is self-contained."
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
# Serialize prompt as JSON string and call Gemini API (30s timeout)
|
|
52
|
+
PROMPT_JSON=$(printf '%s' "You are a senior QA engineer. Review whether this PR diff satisfies the Acceptance Criteria below.\n\nACCEPTANCE CRITERIA:\n${AC_CONTEXT}\n\nPR DIFF:\n${DIFF}\n\nRespond with exactly one word: PASS or FAIL, then on the next line explain briefly why (max 3 sentences)." | python3 -c 'import json,sys; print(json.dumps(sys.stdin.read()))')
|
|
53
|
+
|
|
54
|
+
RESPONSE=$(curl -s -X POST \
|
|
55
|
+
"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${GEMINI_API_KEY}" \
|
|
56
|
+
-H "Content-Type: application/json" \
|
|
57
|
+
-d "{\"contents\":[{\"parts\":[{\"text\":${PROMPT_JSON}}]}]}" \
|
|
58
|
+
--max-time 30 || echo "API_ERROR")
|
|
59
|
+
|
|
60
|
+
if [ "$RESPONSE" = "API_ERROR" ]; then
|
|
61
|
+
gh pr edit "$PR_NUMBER" --add-label "infra:qa-broken"
|
|
62
|
+
gh pr comment "$PR_NUMBER" --body "🤖 **QA Agent:** Could not reach Gemini API. Manual review required."
|
|
63
|
+
exit 0
|
|
64
|
+
fi
|
|
65
|
+
|
|
66
|
+
VERDICT=$(echo "$RESPONSE" | python3 -c 'import json,sys; d=json.load(sys.stdin); t=d.get("candidates",[{}])[0].get("content",{}).get("parts",[{}])[0].get("text","").strip(); print(t.split("\n")[0].upper() if t else "API_ERROR")')
|
|
67
|
+
EXPLANATION=$(echo "$RESPONSE" | python3 -c 'import json,sys; d=json.load(sys.stdin); t=d.get("candidates",[{}])[0].get("content",{}).get("parts",[{}])[0].get("text","").strip(); lines=t.split("\n",1); print(lines[1].strip() if len(lines)>1 else "")')
|
|
68
|
+
|
|
69
|
+
if echo "$VERDICT" | grep -q "^PASS"; then
|
|
70
|
+
gh pr edit "$PR_NUMBER" --add-label "qa:approved"
|
|
71
|
+
gh pr comment "$PR_NUMBER" --body "🤖 **QA Agent:** AC coverage verified. ${EXPLANATION}"
|
|
72
|
+
elif echo "$VERDICT" | grep -q "^FAIL"; then
|
|
73
|
+
gh pr edit "$PR_NUMBER" --add-label "qa:needs-work"
|
|
74
|
+
gh pr comment "$PR_NUMBER" --body "🤖 **QA Agent:** AC coverage insufficient. ${EXPLANATION}"
|
|
75
|
+
else
|
|
76
|
+
gh pr edit "$PR_NUMBER" --add-label "infra:qa-broken"
|
|
77
|
+
gh pr comment "$PR_NUMBER" --body "🤖 **QA Agent:** Could not parse Gemini response. Manual review required."
|
|
78
|
+
fi
|
package/templates/AGENTS.md
CHANGED
|
@@ -70,6 +70,7 @@ When detailing a solution in an issue body, you must **always** include both the
|
|
|
70
70
|
- Never open a PR without passing the tests.
|
|
71
71
|
- Never implement beyond the immediate scope of the issue.
|
|
72
72
|
- Never create future-proofing abstractions for hypothetical features.
|
|
73
|
+
- Never add or remove `stage:*` or `qa:*` labels manually. These are owned by GitHub Actions automation and the PM only.
|
|
73
74
|
{{EXTRA_DONT}}
|
|
74
75
|
|
|
75
76
|
## Project Standards
|
package/templates/docs/pdlc.md
CHANGED
|
@@ -22,7 +22,7 @@ Adapt columns as needed. The functional baseline is:
|
|
|
22
22
|
## Workflow Variants (QA Agent)
|
|
23
23
|
|
|
24
24
|
- **Variant A (Default):** PRs bypass the `Testing` column and land directly in `Code Review / PR`.
|
|
25
|
-
- **Variant B (QA Agent Enabled):** PRs land in the `Testing` column first. An AI QA agent verifies the PR, adding `qa:
|
|
25
|
+
- **Variant B (QA Agent Enabled):** PRs land in the `Testing` column first. An AI QA agent verifies the PR, adding `qa:approved` or `qa:needs-work`. Only after a `qa:approved` is the issue moved to `Code Review / PR`.
|
|
26
26
|
|
|
27
27
|
## Board Identifiers (GitHub Projects)
|
|
28
28
|
|
|
@@ -77,6 +77,9 @@ REPO = {{REPO_OWNER}}/{{REPO_NAME}}
|
|
|
77
77
|
| `spec:approved` | Issue | Green | Gate 2 — agent is cleared to implement |
|
|
78
78
|
| `pr:in-review` | PR | Yellow | Awaiting code review |
|
|
79
79
|
| `pr:approved` | PR | Green | Code review approved |
|
|
80
|
+
| `qa:approved` | PR | Green | QA Agent passed — AC coverage verified |
|
|
81
|
+
| `qa:needs-work` | PR | Red | QA Agent failed — PR needs changes |
|
|
82
|
+
| `infra:qa-broken` | PR | Orange | QA Agent error — manual review required |
|
|
80
83
|
|
|
81
84
|
## Approval Gates
|
|
82
85
|
|